I trying to add custom properties to the ApplicationUser for a web site using MVC5 and OWIN authentication. I\'ve read https://stackoverflow.com/a/10524305/264607 and I like ho
I can get something to work using Claims
based security, so if you're looking to get something done quickly here is what I have at the moment:
In the login process in the AccountController
(mine is within SignInAsync
method), add a new claim to the identity created by UserManager
:
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim("PatientNumber", user.PatientNumber)); //This is what I added
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
Then in my base controller classes I simply added a property:
private string _patientNumber;
public string PatientNumber
{
get
{
if (string.IsNullOrWhiteSpace(_patientNumber))
{
try
{
var cp = ClaimsPrincipal.Current.Identities.First();
var patientNumber = cp.Claims.First(c => c.Type == "PatientNumber").Value;
_patientNumber = patientNumber;
}
catch (Exception)
{
}
}
return _patientNumber;
}
}
This link was helpful for claims knowledge: http://msdn.microsoft.com/en-us/library/ms734687.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1
Update for the issue with IPrincipal
I tracked it down to the Identity
property. The issue was that I was providing a default constructor on the PatientPortalPrincipal
class that was not setting the Identity property. What I ended up doing was removing the default constructor and calling the correct constructor from within Application_PostAuthenticateRequest
, updated code is below
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
ApplicationUser user = userManager.FindByName(HttpContext.Current.User.Identity.Name);
PatientPortalPrincipal newUser = new PatientPortalPrincipal(user);
newUser.BirthDate = user.BirthDate;
newUser.InvitationCode = user.InvitationCode;
newUser.PatientNumber = user.PatientNumber;
//Claim cPatient = new Claim(typeof(PatientPortalPrincipal).ToString(), );
HttpContext.Current.User = newUser;
}
}
That makes the whole thing work!
I've had the same error.
My problem was that with anonymous users I wasn't setting the IIdentity on IPrincipal. I did this only when users logged in with user name. Otherwise, IIdentity was null.
My solution was to always set IIdentity. If user is not authenticated (anonymous user) then IIdentity.IsAuthenticated is set to false. Otherwise, true.
My code:
private PrincipalCustom SetPrincipalIPAndBrowser()
{
return new PrincipalCustom
{
IP = RequestHelper.GetIPFromCurrentRequest(HttpContext.Current.Request),
Browser = RequestHelper.GetBrowserFromCurrentRequest(HttpContext.Current.Request),
/* User is not authenticated, but Identity must be set anyway. If not, error occurs */
Identity = new IdentityCustom { IsAuthenticated = false }
};
}
You're getting an exception because HttpContext.Current.User.Identity.IsAuthenticated
returns false at the point of check (so does HttpContext.Current.Request.IsAuthenticated
).
If you remove the if (HttpContext.Current.User.Identity.IsAuthenticated)
statement it will work fine (at least this part of code).
I've tried a simple thing like this:
BaseController.cs
public abstract class BaseController : Controller
{
protected virtual new CustomPrincipal User
{
get { return HttpContext.User as CustomPrincipal; }
}
}
CustomPrincipal.cs
public class CustomPrincipal : IPrincipal
{
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) { return false; }
public CustomPrincipal(string username)
{
this.Identity = new GenericIdentity(username);
}
public DateTime BirthDate { get; set; }
public string InvitationCode { get; set; }
public int PatientNumber { get; set; }
}
Global.asax.cs
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
CustomPrincipal customUser = new CustomPrincipal(User.Identity.Name);
customUser.BirthDate = DateTime.Now;
customUser.InvitationCode = "1234567890A";
customUser.PatientNumber = 100;
HttpContext.Current.User = customUser;
}
HomeController.cs
public ActionResult Index()
{
ViewBag.BirthDate = User.BirthDate;
ViewBag.InvitationCode = User.InvitationCode;
ViewBag.PatientNumber = User.PatientNumber;
return View();
}
And this is working fine. So unless this code:
userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
ApplicationUser user = userManager.FindByName(HttpContext.Current.User.Identity.Name);
is not returning a valid (custom) user object, the problem is with the if()
statement.
Your update looks fine, and if you're happy to store data as claims in a cookie you can go with it, although I personally hate the try {}
catch block there.
What I do instead is this:
BaseController.cs
[AuthorizeEx]
public abstract partial class BaseController : Controller
{
public IOwinContext OwinContext
{
get { return HttpContext.GetOwinContext(); }
}
public new ClaimsPrincipal User
{
get { return base.User as ClaimsPrincipal; }
}
public WorkContext WorkContext { get; set; }
}
I decorate the base controller class with a custom attribute.
AuthorizeExAttribute.cs:
public class AuthorizeExAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
Ensure.Argument.NotNull(filterContext);
base.OnAuthorization(filterContext);
IPrincipal user = filterContext.HttpContext.User;
if (user.Identity.IsAuthenticated)
{
var ctrl = filterContext.Controller as BaseController;
ctrl.WorkContext = new WorkContext(user.Identity.Name);
}
}
}
And WorkContext.cs:
public class WorkContext
{
private string _email;
private Lazy<User> currentUser;
private IAuthenticationService authService;
private ICacheManager cacheManager;
public User CurrentUser
{
get
{
var cachedUser = cacheManager.Get<User>(Constants.CacheUserKeyPrefix + this._email);
if (cachedUser != null)
{
return cachedUser;
}
else
{
var user = currentUser.Value;
cacheManager.Set(Constants.CacheUserKeyPrefix + this._email, user, 30);
return user;
}
}
}
public WorkContext(string email)
{
Ensure.Argument.NotNullOrEmpty(email);
this._email = email;
this.authService = DependencyResolver.Current.GetService<IAuthenticationService>();
this.cacheManager = DependencyResolver.Current.GetService<ICacheManager>();
this.currentUser = new Lazy<User>(() => authService.GetUserByEmail(email));
}
I then access the WorkContext like this:
public class DashboardController : BaseController
{
public ActionResult Index()
{
ViewBag.User = WorkContext.CurrentUser;
return View();
}
}
I'm using Ninject's Dependency Resolver to resolve authService
and cacheManager
but you can skip caching and replace authService with ASP.NET Identity UserManager
I believe.
I also wanted to give credit where it's due as the WorkContext class is heavily inspired by NugetGallery project.
I bet HttpContext.Current.User is null. So instead of this:
if (HttpContext.Current.User.Identity.IsAuthenticated)
you can try this:
if (HttpContext.Current.Request.IsAuthenticated)