I’ve been playing around with Authorize attributes in .NET Core as of late. With a custom (derived) authorization attribute, it seemed like it wasn’t always being evaluated. This caused me to pause and take a step back to examine using the built-in policy-based authorization.
Most of my authorization from .NET 4.6.x was already using a policy-based approach. I would have a set of “policy keys” which would return a list of claims-based roles from a group of dictionaries. The premise is simple:
Keys contained in a static class:
public static class AuthorizePolicies { public const string Auth1 = "Auth1"; public const string Auth2 = "Auth2"; public const string Auth3 = "Auth3"; }
And then I have an interface that has two simple methods.. Get roles based on the policy key and check if the user is in the roles pointed to be a policy key:
public interface IDomainRoles { string[] GetRoles(string key); bool IsInDomainRole(string key); }
The implementation, then, would define the the arrays of Claims/Roles based on the policy keys and utilize the HttpContext to determine if the user has particular claims. The interface methods allow interacting with the lists/arrays of Claims to check for their presence on the current ClaimsPrincipal:
public class DomainRoles : IDomainRoles { private IHttpContextAccessor _contextAccessor; private ILogger _log; public DomainRoles(IHttpContextAccessor contextAccessor, ILogger<IDomainRoles> log) { _contextAccessor = contextAccessor; _log = log; } private static readonly Dictionary<string, string[]> _areaRoles = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase) { { AuthorizePolicies.Auth1, new string[] { "Claim1", "Claim2" } }, { AuthorizePolicies.Auth2, new string[] { "Claim1", "Claim2" } }, { AuthorizePolicies.Auth3, new string[] { "Claim1", "Claim2" } } }; private HttpContext CurrentContext { get { return _contextAccessor.HttpContext; } } /// <summary> /// Returns the principal from the HttpContext or Thread /// </summary> private IPrincipal CurrentUser { get { IPrincipal principal = null; var context = CurrentContext; if (context != null) { principal = context.User; } else { principal = ClaimsPrincipal.Current; } return principal; } } private bool IsLoggedIn { get { var isLoggedIn = false; var user = CurrentUser; if (user != null && user.Identity.IsAuthenticated) { var identity = user.Identity as ClaimsIdentity; if (identity != null) { isLoggedIn = true; } } return isLoggedIn; } } private bool IsInRole(string role) { var isInRole = false; var user = CurrentUser; if (user != null) { isInRole = user.IsInRole(role); } return isInRole; } public string[] GetRoles(string key) { string[] retValue = null; if (!IsLoggedIn) return null; try { retValue = _areaRoles[key]; } catch (Exception ex) { _log.LogError($"Error retrieving roles for {key}", ex); } return retValue; } public bool IsInDomainRole(string roleKey) { var roles = GetRoles(roleKey); var rolesList = new List<string>(); if (roles != null && roles.Length > 0) { foreach (var role in roles) { var strRole = role.ToString(); if (IsInRole(strRole)) { return true; } } } return false; } }
After creating these bits, I can easily check if the user has access through a particular “policy:”
var domainRoles = ServiceProviderFactory.ServiceProvider.GetService<IDomainRoles>(); var isInRole = domainRoles.IsInDomainRole(policyKey);
Now, how about integrating this with the built-in .NET Core Authorization? This becomes rather easy. In our Startup.cs, We need to use reflection to retrieve all of our policy keys, iterate over them, and add a policy per key that calls our IDomainRoles to check if the user is in the list of Claims provided by the policy key. This is achieved by using the “RequireAssertion” method that is available on the AuthorizationPolicyBuilder:
public static class ServiceProviderFactory { // This gets set in the Startup.cs Configure method from IApplicationBuilder.ApplicationServices public static IServiceProvider ServiceProvider { get; set; } } // Add policies services.AddAuthorization(options => { var fieldInfos = typeof(AuthorizePolicies).GetFields().ToList(); foreach(var fieldInfo in fieldInfos) { var policyKey = fieldInfo.GetValue(null) as string; options.AddPolicy(policyKey, p => { p.RequireAuthenticatedUser(); p.RequireAssertion(context => { var domainRoles = ServiceProviderFactory.ServiceProvider.GetService<IDomainRoles>(); return domainRoles.IsInDomainRole(policyKey); }); }); } });
Finally, the only thing else that’s needed is decorate our controllers/controller actions with the standard Authorize attribute while specifying a policy.
[Authorize(AuthorizePolicies.Auth1)] public class MyController : Controller { }
I like this approach a lot since it is flexible without needing customized attributes. The fluent API that’s available through the policy builder meshes well with the Claim-based security I already use within my domain-layer.