Search code examples
asp.net-mvcmodel-view-controllerasp.net-coreasp.net-identity

Asp Core MVC 2.1 Authorization based on policies per user?


In my web application I would like to apply policies according to the user in such a way. For example I have 3 policies;

  1. Allow to enter on Mondays.
  2. Allow entry at a certain hour.
  3. Prohibited to enter.
  4. ...

User John has activated policy 1, and user Richard has activated only policy 2. For example, if user John registers ok in my application but in a certain controller will not allow the step, because it's Monday. These conditions can be changed in execution time. How can I add policies dynamically in an action controller?

I've been looking, but what I see in asp.net core is that I have to register the policies to the startup and that does not fit in to my logic.


Solution

  • I think there are two keys to solving this cleanly:

    Multiple Handlers

    First, create a simple requirement:

    public class CustomPolicyRequirement : IAuthorizationRequirement { }
    

    Then, create the needed handlers for it. I made some very simple example handlers based on the policies you described:

    public class MondaysRequirementHandler : AuthorizationHandler<CustomPolicyRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomPolicyRequirement requirement)
        {
            // Allow John in on Mondays
            if (DateTime.Now.DayOfWeek == DayOfWeek.Monday && context.User.Identity.Name == "John")
                context.Succeed(requirement);
    
            return Task.CompletedTask;
        }
    }
    
    public class EveningsOnlyRequirementHandler : AuthorizationHandler<CustomPolicyRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomPolicyRequirement requirement)
        {
            var now = DateTime.Now;
    
            // Allow Richard in, as long as it's between 18:00 and 22:00
            if (now.Hour >= 18 && now.Hour < 22 && context.User.Identity.Name == "Richard")
                context.Succeed(requirement);
    
            return Task.CompletedTask;
        }
    }
    
    public class ProhibitedUserRequirementHandler : AuthorizationHandler<CustomPolicyRequirement>
    {
        readonly IProhibitedUsersService prohibitedUsersService;
    
        public ProhibitedUserRequirementHandler(IProhibitedUsersService prohibitedUsersService)
            => this.prohibitedUsersService = prohibitedUsersService;
    
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomPolicyRequirement requirement)
        {
            // Don't let prohbited users in, even if other handlers are satisfied
            if (prohibitedUsersService.IsUserProhibited(context.User.Identity))
                context.Fail();
    
            return Task.CompletedTask;
        }
    }
    

    Since all of the handlers are associated with CustomPolicyRequirement, the requirement will be satisfied as long as at least one handler succeeds, and non of them fail. Handlers that neither succeed nor fail do not affect the outcome.

    Dynamic Data

    The third example handler depends on a dependency-injected service:

    public interface IProhibitedUsersService
    {
        bool IsUserProhibited(IIdentity user);
    }
    

    That service could be implemented to fetch data from a SQL database (or any other source), which would allow rules to be adjusted while the application is running.