Click here to Skip to main content
15,885,278 members
Articles / Web Development / ASP.NET / ASP.NET Core

AttributeAuthorization with Custom Roles in ASP.NET Core

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
17 Feb 2017CPOL4 min read 43.4K   1.5K   11   5
Use a custom Authorize Attribute which use a own PermissionProvider with custom Permission-Management.

Sample Project which implements this Authorize Attribute.

Introduction

You have a self implemented Authentication Service (User-Permission Management) and want use simple Attributes to control Access about your Controllers or Action-Methods?

Then you're exactly right here.

With this example you can use your existing Permission-Management in a ASP.NET Core Application.

Attention this is not for Authentication, you still need something like Windowsauthentication.
This is only a Way to authorize a User with some Permissions.

My Example is a ASP.NET Core WebApi Project with WindowsAuthentication.

Background

I have a own Permission-Management in a Database. There i manage the Users with their Permissions, like an Active Directory, but with some more delegate Options. So I want use a simple Authorize Attribute to set required Permissions for Controllers or Action-Methods. Example which I used in ASP.NET 4.5: [Authorize(Roles=”Administrator”)]

Preparations

We need first the Permissions and Users from the Database or whatever.

In my example we see only the IUserCache Interface which implements a Cached Repository of Users. And this Users have a Property as a List of Permissions.

The UserCache reloads the User if the Cachetime of this is expired or this User do not exists in Cache.

Nuget Packages (.NET Core Extensions)

  • Microsoft.AspNetCore.Authentication

  • Microsoft.NETCore.App

  • Microsoft.AspNetCore.Mvc.Core

5 Steps to Authorize

  1. Own Principal-Implementation

  1. Permission-Provider (Get the Permissions)

  1. Custom Authorize Attribute

  1. Authorization-Requirement

  1. Set Dependencies

1. Own Principal Implementation

We create a Principal-Class deriven from ClaimsPrincipal.

This Class override the IsInRole with our PermissionProvider to check the Role. Our Principal needs our PermissionProvider, which will check the Roles from this User.

C#
public class AppPrincipal : ClaimsPrincipal { 

        private readonly IPermissionProvider _PermissionProvider; 

        public AppPrincipal(IPermissionProvider permissionProvider, IIdentity ntIdentity) : 
        base((ClaimsIdentity) ntIdentity) 
        { 
            _PermissionProvider = permissionProvider; 
        } 

        public override bool IsInRole(string role) { 
            return _PermissionProvider.IsUserAuthorized(this, role); 
        } 
    }

2. Permission-Provider

C#
public class PermissionProvider : IPermissionProvider { 
        private readonly IServerConfiguration _ServerConfiguration; 
        private readonly IUserCache _UserCache; 
        private readonly string _AdministratorPermission; 
      
        public PermissionProvider( 
            IServerConfiguration serverConfiguration 
            , IUserCache userCache) { 

            _ServerConfiguration = serverConfiguration; 
            _UserCache = userCache; 
            _AdministratorPermission = _ServerConfiguration.AdministratorPermissionName; 
        } 

        public bool IsUserAuthorized(IPrincipal principal, string permission) { 
            if (string.IsNullOrWhiteSpace(permission)) return true; 
            var user = _UserCache.GetUserFromPrincipal(principal); 
            if (user == null) return false; 

            //User has Permission and Permission is Valid 
            if (user.ApplicationPermissions.Any(i => i.IsValid 
                   && i.Permission.Equals(permission, StringComparison.OrdinalIgnoreCase))) { 

                return true; 
            } 

            //User has Administrator Permission and Permission is Valid 
            if (user.ApplicationPermissions.Any(i => i.IsValid 
                   && i.Permission.Equals(_AdministratorPermission, StringComparison.OrdinalIgnoreCase))) { 

                return true; 
            } 
            return false; 
        } 
    } 

Our UserAccount-Class has a List of ApplicationPermission which have

  • a IsValid-Flag => (Checks if not Expired)
  • Permission-Name => (“Administrator”)

This Provider checks only if the User has the Permission we want to check.
I defined a Master Permission, which have Access to all. This i stored as Setting in my Config(_ServerConfiguration.AdministratorPermissionName)

The User we get from our own IUserCache-Interface, which reloads the User if it is expired. (You can check here direct with Database if you want, but i cache the Permissions for some Minutes)

How you want implement the Database-Access you can choose for yourself.

The Dependencies we get from the .NET Core DI

3. Custom Authorize Attribute

We need a Custom Attribute which execute the Permission-Check.

C#
public class RequiresPermissionAttribute : TypeFilterAttribute { 

        public RequiresPermissionAttribute(params string[] permissions) 
             : base(typeof(RequiresPermissionAttributeExecutor)) { 

            Arguments = new[] { new PermissionAuthorizationRequirement(permissions) }; 
        }


        private class RequiresPermissionAttributeExecutor : Attribute, IAsyncResourceFilter { 
            private readonly ILogger _logger; 
            private readonly PermissionAuthorizationRequirement _requiredPermissions; 
            private readonly IPermissionProvider _PermissionProvider; 

            public RequiresPermissionAttributeExecutor(ILogger<RequiresPermissionAttribute> logger, 
                                            PermissionAuthorizationRequirement requiredPermissions, 
                                            IPermissionProvider permissionProvider) { 

                _logger = logger; 
                _requiredPermissions = requiredPermissions; 
                _PermissionProvider = permissionProvider; 
            } 

            public async Task OnResourceExecutionAsync(ResourceExecutingContext context, 
                                                       ResourceExecutionDelegate delegate) { 

                var principal = new AppPrincipal(_PermissionProvider, context.HttpContext.User.Identity); 
                bool isInOneOfThisRole = false; 

                foreach (var item in _requiredPermissions.RequiredPermissions) { 
                    if (principal.IsInRole(item)) { 
                        isInOneOfThisRole = true; 
                    } 
                } 

                if (isInOneOfThisRole == false) { 
                    context.Result = new UnauthorizedResult(); 
                    await context.Result.ExecuteResultAsync(context); 
                } 
                else { 
                    await delegate(); 
                } 
            } 
        } 
    } 

The RequiresPermissionAttribute need an Baseclass which implements the IAsyncResourceFilter Interface.

In the RequiresPermissionAttributeExecutor we create our own AppPrincipal Object from the current ClaimsPrincipal.

This Executor get our PermissionProvider from the Dependency Injection, and this we have to inject in our AppPrincipal-Object, that it can checks the Permissions.

With this we can use the IsInRole Method and if one of the Permissions exists we get the Access. So we have a “Or” Statement.
Example: => User need one of this

C#
[RequiresPermission("Permission1","Permission2")] 

If you want require multiple Roles you can set multiple Attributes (not Parameters)
Example: => User needs both

C#
[RequiresPermission("Permission1")]  
[RequiresPermission("Permission2")] 
public class TestController : Controller 

On mismatch, we get an Unauthorized Error (Statuscode 401)

4. Authorization Requirement

The Authorization Requirement represents the Parameters, which we set in the Attribute. Example: (“Permission1”)

C#
public class PermissionAuthorizationRequirement : IAuthorizationRequirement { 

        public IEnumerable<string> RequiredPermissions { get; } 

        public PermissionAuthorizationRequirement(IEnumerable<string> requiredPermissions) { 
            RequiredPermissions = requiredPermissions; 
        } 
    } 

5. Set Dependencies

Now we have to set the Configuration for the Dependency Injection.

This we find in the Startup.cs Class and need to add the Dependencies in the ConfigureServices.

We have

  • PermissionProvider (IPermissionProvider)

  • UserCache (IUserCache)

  • ServerConfiguration (IServerConfiguration)

C#
public void ConfigureServices(IServiceCollection services) 
        { 
            // Add framework services. 
            services.AddMvc(); 

            services.AddSingleton<IServerConfiguration, ServerConfiguration>(); 
            services.AddSingleton<IUserCache, UserCache>(); 

            services.AddTransient<IPermissionProvider, PermissionProvider>(); 
        }

That's all!

Now it will works.

Example how it looks in Controller

C#
[RequiresPermission(Permission.User)]
public class UserAccountController : Controller {

    public async Task<IActionResult> Get() {
        try {
            return Ok();
        }
        catch (Exception e) {
            return BadRequest(e);
        }
    }

    [RequiresPermission(Permission.Manager)]
    public async Task<IActionResult> GetManager() {
        try {
            return Ok();
        }
        catch (Exception e) {
            return BadRequest(e);
        }
    }
}

The first Method Get() can use all Users which have the Permission “User”. The second Method GetManager() can only use Users which have the Permission “User” and “Manager”.

Because firstly will be checked if the User have the Permission which is defined on the Controller. After this, it will checks for the Permissions defined on the Method.

You can use multiple Permissions as Attributeparameters to get a “Or” functionality, or you can use multiple Permissions as Attributes to get a “And” functionality.

Example OR:

C#
[RequiresPermission(Permission.User, Permission.Manager)] 
public class UserAccountController : Controller 

User which have the Permission «User» OR «Manager» have access.

Example AND:

C#
[RequiresPermission(Permission.User)] 
[RequiresPermission(Permission.Manager)] 
public class UserAccountController : Controller  

User which have the Permissions «User» AND «Manager» have access.
The User need both not only one of this!

What is Permission.User?

This is static Class Permission which have all possible Permissions (Refactorable)

C#
public static class Permission { 
        public const string Manager = nameof(Manager); 
        public const string User = nameof(User); 
    }

History

17.02.2017 Created

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Junior)
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionIPermissionProvider not found Pin
Carlos de Jesus Baez24-Apr-19 11:38
Carlos de Jesus Baez24-Apr-19 11:38 
QuestionDatamodel with attribute Pin
vibhakar singh24-Jan-19 18:46
vibhakar singh24-Jan-19 18:46 
QuestionEntityFramework Pin
Member 1269327919-Apr-18 16:20
Member 1269327919-Apr-18 16:20 
PraiseBeautiful! Pin
Derek Broughton5-Jul-17 3:18
Derek Broughton5-Jul-17 3:18 
QuestionSend to an error page instead of a windows login prompt? Pin
chrismilne19-May-17 5:23
chrismilne19-May-17 5:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.