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)
5 Steps to Authorize
-
Own Principal-Implementation
-
Permission-Provider (Get the Permissions)
-
Custom Authorize Attribute
-
Authorization-Requirement
-
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.
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
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;
if (user.ApplicationPermissions.Any(i => i.IsValid
&& i.Permission.Equals(permission, StringComparison.OrdinalIgnoreCase))) {
return true;
}
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.
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
[RequiresPermission("Permission1","Permission2")]
If you want require multiple Roles you can set multiple Attributes (not Parameters)
Example: => User needs both
[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”)
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
public void ConfigureServices(IServiceCollection 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
[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:
[RequiresPermission(Permission.User, Permission.Manager)]
public class UserAccountController : Controller
User which have the Permission «User» OR «Manager» have access.
Example AND:
[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)
public static class Permission {
public const string Manager = nameof(Manager);
public const string User = nameof(User);
}
History
17.02.2017 Created