9

I was trying to make a custom authorization attribute in ASP.NET vNext, until I found this excelent answer from @blowdart in this post:

https://stackoverflow.com/a/31465227/1756978

indicating that Authorization requirements is now the way to go. The answer is very clarifying but doesn't indicates how to pass a parameter to this requirements / policies.

What I'm trying to do is porting a MVC 5 custom authorization attribute which has this signature:

[Autorizacion(Requires = enumPermission.DeleteCustomer)]

since I use a very customised set of permissions mirrored in the backend/frontend as enums/strings.

As this features are still not documented I feel a little lost... Could anybody give guidance about?

Thanks in advance

Lukas
  • 549
  • 1
  • 4
  • 16
Vi100
  • 3,584
  • 21
  • 37

2 Answers2

2

I happen to comes up with a workround that can satisfy my requirement, hope it will help your too.

In my case, I need to pass IHttpContextAccessor and EFCore's AppDbContext to my Requirement class.

in my Startup.cs, I write something like this:

services.AddAuthorization(options =>
{
    options.AddPolicy("ThePolicy", policy => policy.Requirements.Add( new ThePolicyRequirement() ));
});
services.AddScoped<IAuthorizationHandler, ThePolicyAuthorizationHandler>();

the ThePolicyAuthorizationHandler class:

public class ThePolicyAuthorizationHandler : AuthorizationHandler<ThePolicyRequirement>
{
    readonly AppDbContext _appContext;
    readonly IHttpContextAccessor _contextAccessor;

    public ThePolicyAuthorizationHandler(AppDbContext c, IHttpContextAccessor ca)
    {
        _appContext = c;
        _contextAccessor = ca;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ThePolicyRequirement requirement)
    {
        var result = await requirement.isPass(_appContext, _contextAccessor, context);
        if (result)
            context.Succeed(requirement);
        else
            context.Fail(requirement);
    }
}

and ThePolicyRequirement class:

public class ThePolicyRequirement : IAuthorizationRequirement
{
    AppDbContext _context;
    IHttpContextAccessor _contextAccessor;
    AuthorizationHandlerContext _authHandlerContext;

    public async Task<bool> isPass(AppDbContext context, IHttpContextAccessor contextAccessor, AuthorizationHandlerContext authorizationHandlerContext)
    {
        _context = context;
        _contextAccessor = contextAccessor;
        _authHandlerContext = authorizationHandlerContext;

        //logic here

        return result;
    }
}

The key idea is using ThePolicyAuthorizationHandler to obtain as much as possible all needed objects, and pass it to ThePolicyRequirementto do the logic of the authorization mechanism.

Riza
  • 907
  • 7
  • 18
0

Indeed, @blowdart’s post is very insightful and from my understanding, the key thing to understand is the following:

Authorization act upon Identities. Identities are created by authentication.

So it seems that identities are created by the authentication process.

Then (if you wish) you can make the authorization process kick in. This means creating a custom authorization requirements to which this requirement will be looking at those identities and act upon them.

In plain English, this is what I believe is happening:

  1. As mentioned in blowdart’s post, we should have some sort of authentication middleware that happens to do the actual authentication. Once successfully authenticated, you take whatever information you want from that now-authenticated user and create an authenticated ClaimsPrincipal.

    For example, we could store into that ClaimsPrincipal, the sets of permission the user has.

  2. Then, when you create your authorization requirement you look at the ClaimsPrincipal, extract the sets of permissions from the ClaimsPrincipal and take appropriate action based on whatever business rules you want.

    Assuming you can’t store the sets of permission into the ClaimsPrincipal for some reason, one could easily store the UserId and from within the requirement, read that UserId from the ClaimsPrincipal, invoke the database and get the sets of permissions and then act upon them.

Conclusion:

So in short, I don’t think you pass stuff to the requirement(s), I think you obtain them from within a ClaimsPrincipal.

In your example, you could create a requirement that reads the ClaimsPrincipal and compare whatever value with your Enum and act upon that.

Let us know what you’ve managed to do and if it works.

And if my understanding of this is wrong, then by all means, feel free to correct me since all of this is new stuff :-)

Vlince
  • 5,633
  • 7
  • 42
  • 59
  • 1
    Yes, I'm passing stuff to the requirements... The claim contains ALL the user permissions marked in a bit array codified in a base64 string. Against that I need to check if a concrete permission is granted to filter the authorization in a given api action, that's why I need to pass this concrete permission which is chosen from an enum mapped to the previous array. That way I can check the permission ultra fast without reading the database, and I have intellisense on them since they are part of an enum. That's currently implemented in MVC 5 and works like a charm. – Vi100 Aug 25 '15 at 15:08
  • @Vi100 I am currently working on an ASP.NET MVC 5 project where I want to dynamically assign user roles to controllers via a database table. Am I correct to understand that you already have this implemented in your ASP.NET MVC 5 application? If yes, are you willing to share it? If no, can you please point me to the relevant documentation that you used to develop the solution? Any consideration/assistance is greatly appreciated! – J Weezy May 15 '17 at 00:21