I have an MVC5 app that uses Individual Authentication, and of course ASP.NET Identity. The point is that I had extended I have a model that inherits from ApplicationUser, i
In MVC5 the user data is stored by default in the session and upon request the data is parsed into a ClaimsPrincipal which contains the username (or id) and the claims.
This is the way I chose to implement it, it might not be the simplest solution but it definitely makes it easy to use.
Example of usage:
In controller:
public ActionResult Index()
{
ViewBag.ReverseDisplayName = this.User.LastName + ", " + this.User.FirstName;
}
In view or _Layout:
@if(User.IsAuthenticated)
{
@User.DisplayName
}
1. Replace ClaimsIdentityFactory
using System.Security.Claims;
using System.Threading.Tasks;
using Domain.Models;
using Microsoft.AspNet.Identity;
public class AppClaimsIdentityFactory : IClaimsIdentityFactory
{
internal const string IdentityProviderClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";
internal const string DefaultIdentityProviderClaimValue = "My Identity Provider";
///
/// Constructor
///
public AppClaimsIdentityFactory()
{
RoleClaimType = ClaimsIdentity.DefaultRoleClaimType;
UserIdClaimType = ClaimTypes.NameIdentifier;
UserNameClaimType = ClaimsIdentity.DefaultNameClaimType;
SecurityStampClaimType = Constants.DefaultSecurityStampClaimType;
}
///
/// Claim type used for role claims
///
public string RoleClaimType { get; set; }
///
/// Claim type used for the user name
///
public string UserNameClaimType { get; set; }
///
/// Claim type used for the user id
///
public string UserIdClaimType { get; set; }
///
/// Claim type used for the user security stamp
///
public string SecurityStampClaimType { get; set; }
///
/// Create a ClaimsIdentity from a user
///
///
///
///
///
public virtual async Task CreateAsync(UserManager manager, User user, string authenticationType)
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
if (user == null)
{
throw new ArgumentNullException("user");
}
var id = new ClaimsIdentity(authenticationType, UserNameClaimType, RoleClaimType);
id.AddClaim(new Claim(UserIdClaimType, user.Id.ToString(), ClaimValueTypes.String));
id.AddClaim(new Claim(UserNameClaimType, user.UserName, ClaimValueTypes.String));
id.AddClaim(new Claim(IdentityProviderClaimType, DefaultIdentityProviderClaimValue, ClaimValueTypes.String));
id.AddClaim(new Claim(ClaimTypes.Email, user.EmailAddress));
if (user.ContactInfo.FirstName != null && user.ContactInfo.LastName != null)
{
id.AddClaim(new Claim(ClaimTypes.GivenName, user.ContactInfo.FirstName));
id.AddClaim(new Claim(ClaimTypes.Surname, user.ContactInfo.LastName));
}
if (manager.SupportsUserSecurityStamp)
{
id.AddClaim(new Claim(SecurityStampClaimType,
await manager.GetSecurityStampAsync(user.Id)));
}
if (manager.SupportsUserRole)
{
user.Roles.ToList().ForEach(r =>
id.AddClaim(new Claim(ClaimTypes.Role, r.Id.ToString(), ClaimValueTypes.String)));
}
if (manager.SupportsUserClaim)
{
id.AddClaims(await manager.GetClaimsAsync(user.Id));
}
return id;
}
2. Change the UserManager to use it
public static UserManager Create(IdentityFactoryOptions options, IOwinContext context)
{
var manager = new UserManager(new UserStore(new ApplicationDbContext()))
{
ClaimsIdentityFactory = new AppClaimsIdentityFactory()
};
// more initialization here
return manager;
}
3. Create a new custom Principal
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
public class UserPrincipal : ClaimsPrincipal
{
public UserPrincipal(ClaimsPrincipal principal)
: base(principal.Identities)
{
}
public int UserId
{
get { return FindFirstValue(ClaimTypes.NameIdentifier); }
}
public string UserName
{
get { return FindFirstValue(ClaimsIdentity.DefaultNameClaimType); }
}
public string Email
{
get { return FindFirstValue(ClaimTypes.Email); }
}
public string FirstName
{
get { return FindFirstValue(ClaimTypes.GivenName); }
}
public string LastName
{
get { return FindFirstValue(ClaimTypes.Surname); }
}
public string DisplayName
{
get
{
var name = string.Format("{0} {1}", this.FirstName, this.LastName).Trim();
return name.Length > 0 ? name : this.UserName;
}
}
public IEnumerable Roles
{
get { return FindValues(ClaimTypes.Role); }
}
private T FindFirstValue(string type)
{
return Claims
.Where(p => p.Type == type)
.Select(p => (T)Convert.ChangeType(p.Value, typeof(T), CultureInfo.InvariantCulture))
.FirstOrDefault();
}
private IEnumerable FindValues(string type)
{
return Claims
.Where(p => p.Type == type)
.Select(p => (T)Convert.ChangeType(p.Value, typeof(T), CultureInfo.InvariantCulture))
.ToList();
}
}
4. Create an AuthenticationFilter to use it
using System.Security.Claims;
using System.Web.Mvc;
using System.Web.Mvc.Filters;
public class AppAuthenticationFilterAttribute : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
//This method is responsible for setting and modifying the principle for the current request though the filterContext .
//Here you can modify the principle or applying some authentication logic.
var principal = filterContext.Principal as ClaimsPrincipal;
if (principal != null && !(principal is UserPrincipal))
{
filterContext.Principal = new UserPrincipal(principal);
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
//This method is responsible for validating the current principal and permitting the execution of the current action/request.
//Here you should validate if the current principle is valid / permitted to invoke the current action. (However I would place this logic to an authorization filter)
//filterContext.Result = new RedirectToRouteResult("CustomErrorPage",null);
}
}
5. Register the auth filter to load globally in the FilterConfig
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new AppAuthenticationFilterAttribute());
}
By now the Principal is persisted and all we have left to do is expose it in the Controller and View.
6. Create a controller base class
public abstract class ControllerBase : Controller
{
public new UserPrincipal User
{
get { return HttpContext.User as UserPrincipal; }
}
}
7. Create a WebViewPage base class and modify the web.config to use it
public abstract class BaseViewPage : WebViewPage
{
public virtual new UserPrincipal User
{
get { return base.User as UserPrincipal; }
}
public bool IsAuthenticated
{
get { return base.User.Identity.IsAuthenticated; }
}
}
public abstract class BaseViewPage : WebViewPage
{
public virtual new UserPrincipal User
{
get { return base.User as UserPrincipal; }
}
public bool IsAuthenticated
{
get { return base.User.Identity.IsAuthenticated; }
}
}
And the web.config inside the Views folder:
Important!
Do not store too much data on the Principal since this data is passed back and forth on each request.