By jltrem


2015-07-16 20:55:54 8 Comments

I'm trying to make a custom authorization attribute in ASP.NET Core. In previous versions it was possible to override bool AuthorizeCore(HttpContextBase httpContext). But this no longer exists in AuthorizeAttribute.

What is the current approach to make a custom AuthorizeAttribute?

What I am trying to accomplish: I am receiving a session ID in the Header Authorization. From that ID I'll know whether a particular action is valid.

10 comments

@No Refunds No Returns 2020-03-05 22:30:03

As of this writing I believe this can be accomplished with the IClaimsTransformation interface in asp.net core 2 and above. I just implemented a proof of concept which is sharable enough to post here.

public class PrivilegesToClaimsTransformer : IClaimsTransformation
{
    private readonly IPrivilegeProvider privilegeProvider;
    public const string DidItClaim = "http://foo.bar/privileges/resolved";

    public PrivilegesToClaimsTransformer(IPrivilegeProvider privilegeProvider)
    {
        this.privilegeProvider = privilegeProvider;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        if (principal.Identity is ClaimsIdentity claimer)
        {
            if (claimer.HasClaim(DidItClaim, bool.TrueString))
            {
                return principal;
            }

            var privileges = await this.privilegeProvider.GetPrivileges( ... );
            claimer.AddClaim(new Claim(DidItClaim, bool.TrueString));

            foreach (var privilegeAsRole in privileges)
            {
                claimer.AddClaim(new Claim(ClaimTypes.Role /*"http://schemas.microsoft.com/ws/2008/06/identity/claims/role" */, privilegeAsRole));
            }
        }

        return principal;
    }
}

To use this in your Controller just add an appropriate [Authorize(Roles="whatever")] to your methods.

[HttpGet]
[Route("poc")]
[Authorize(Roles = "plugh,blast")]
public JsonResult PocAuthorization()
{
    var result = Json(new
    {
        when = DateTime.UtcNow,
    });

    result.StatusCode = (int)HttpStatusCode.OK;

    return result;
}

In our case every request includes an Authorization header that is a JWT. This is the prototype and I believe we will do something super close to this in our production system next week.

Future voters, consider the date of writing when you vote. As of today, this works on my machine.™ You will probably want more error handling and logging on your implementation.

@Daniel 2020-03-06 13:26:57

What about ConfigureServices? Is it needed to add something?

@No Refunds No Returns 2020-03-06 20:01:29

As discussed elsewhere, yes.

@Walter Vehoeven 2020-01-17 21:16:14

The modern way is AuthenticationHandlers

in startup.cs add

services.AddAuthentication("BasicAuthentication").AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);

public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private readonly IUserService _userService;

        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock,
            IUserService userService)
            : base(options, logger, encoder, clock)
        {
            _userService = userService;
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey("Authorization"))
                return AuthenticateResult.Fail("Missing Authorization Header");

            User user = null;
            try
            {
                var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
                var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
                var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
                var username = credentials[0];
                var password = credentials[1];
                user = await _userService.Authenticate(username, password);
            }
            catch
            {
                return AuthenticateResult.Fail("Invalid Authorization Header");
            }

            if (user == null)
                return AuthenticateResult.Fail("Invalid User-name or Password");

            var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            };
            var identity = new ClaimsIdentity(claims, Scheme.Name);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, Scheme.Name);

            return AuthenticateResult.Success(ticket);
        }
    }

IUserService is a service that you make where you have user name and password. basically it returns a user class that you use to map your claims on.

var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            }; 

Then you can query these claims and her any data you mapped, ther are quite a few, have a look at ClaimTypes class

you can use this in an extension method an get any of the mappings

public int? GetUserId()
{
   if (context.User.Identity.IsAuthenticated)
    {
       var id=context.User.FindFirst(ClaimTypes.NameIdentifier);
       if (!(id is null) && int.TryParse(id.Value, out var userId))
            return userId;
     }
      return new Nullable<int>();
 }

This new way, i think is better than

public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (actionContext.Request.Headers.Authorization != null)
        {
            var authToken = actionContext.Request.Headers.Authorization.Parameter;
            // decoding authToken we get decode value in 'Username:Password' format
            var decodeauthToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(authToken));
            // spliting decodeauthToken using ':'
            var arrUserNameandPassword = decodeauthToken.Split(':');
            // at 0th postion of array we get username and at 1st we get password
            if (IsAuthorizedUser(arrUserNameandPassword[0], arrUserNameandPassword[1]))
            {
                // setting current principle
                Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(arrUserNameandPassword[0]), null);
            }
            else
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            }
        }
        else
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        }
    }

    public static bool IsAuthorizedUser(string Username, string Password)
    {
        // In this method we can handle our database logic here...
        return Username.Equals("test") && Password == "test";
    }
}

@Piotr Śródka 2020-03-30 11:04:04

This brilliant answer just works like a charm! Thank you for that and I wish you it will get upvoted, as it is the best answer I have found after like a six hours of searching through blogs, documentation and stack for Basic authentication plus Role authorization.

@Walter Vehoeven 2020-03-30 11:11:05

@PiotrŚródka, you are welcome, please note that the answer is a little "simplified", test if you have a ':' in the text as a malicious user could try and crash your service by simply not playing nice ending in an index out of range exception. as always test what is given to you by external sources

@Abdullah 2019-12-11 09:33:13

For authorization in our app. We had to call a service based on the parameters passed in authorization attribute.

For example, if we want to check if logged in doctor can view patient appointments we will pass "View_Appointment" to custom authorize attribute and check that right in DB service and based on results we will athorize. Here is the code for this scenario:

    public class PatientAuthorizeAttribute : TypeFilterAttribute
    {
    public PatientAuthorizeAttribute(params PatientAccessRights[] right) : base(typeof(AuthFilter)) //PatientAccessRights is an enum
    {
        Arguments = new object[] { right };
    }

    private class AuthFilter : IActionFilter
    {
        PatientAccessRights[] right;

        IAuthService authService;

        public AuthFilter(IAuthService authService, PatientAccessRights[] right)
        {
            this.right = right;
            this.authService = authService;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var allparameters = context.ActionArguments.Values;
            if (allparameters.Count() == 1)
            {
                var param = allparameters.First();
                if (typeof(IPatientRequest).IsAssignableFrom(param.GetType()))
                {
                    IPatientRequest patientRequestInfo = (IPatientRequest)param;
                    PatientAccessRequest userAccessRequest = new PatientAccessRequest();
                    userAccessRequest.Rights = right;
                    userAccessRequest.MemberID = patientRequestInfo.PatientID;
                    var result = authService.CheckUserPatientAccess(userAccessRequest).Result; //this calls DB service to check from DB
                    if (result.Status == ReturnType.Failure)
                    {
                        //TODO: return apirepsonse
                        context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
                    }
                }
                else
                {
                    throw new AppSystemException("PatientAuthorizeAttribute not supported");
                }
            }
            else
            {
                throw new AppSystemException("PatientAuthorizeAttribute not supported");
            }
        }
    }
}

And on API action we use it like this:

    [PatientAuthorize(PatientAccessRights.PATIENT_VIEW_APPOINTMENTS)] //this is enum, we can pass multiple
    [HttpPost]
    public SomeReturnType ViewAppointments()
    {

    }

@ilkerkaran 2019-12-11 10:58:47

Please note that IActionFilter will be a problem when you want to use the same attribute for Hub methods in SignalR.SignalR Hubs expect IAuthorizationFilter

@Abdullah 2019-12-11 13:02:50

Thanks for the info. I am not using SignalR in my application right now so i havent tested it with it.

@Walter Vehoeven 2020-03-30 11:06:58

Same principle I guess as you will still have to use the header's authorisation entry, the implementation will differ

@Gabriel P. 2019-10-05 15:11:11

If anyone just wants to validate a bearer token in the authorize phase using the current security practices you can,

add this to your Startup/ConfigureServices

    services.AddSingleton<IAuthorizationHandler, BearerAuthorizationHandler>();
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();

    services.AddAuthorization(options => options.AddPolicy("Bearer",
        policy => policy.AddRequirements(new BearerRequirement())
        )
    );

and this in your codebase,

public class BearerRequirement : IAuthorizationRequirement
{
    public async Task<bool> IsTokenValid(SomeValidationContext context, string token)
    {
        // here you can check if the token received is valid 
        return true;
    }
}

public class BearerAuthorizationHandler : AuthorizationHandler<BearerRequirement> 
{

    public BearerAuthorizationHandler(SomeValidationContext thatYouCanInject)
    {
       ...
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, BearerRequirement requirement)
    {
        var authFilterCtx = (Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)context.Resource;
        string authHeader = authFilterCtx.HttpContext.Request.Headers["Authorization"];
        if (authHeader != null && authHeader.Contains("Bearer"))
        {
            var token = authHeader.Replace("Bearer ", string.Empty);
            if (await requirement.IsTokenValid(thatYouCanInject, token))
            {
                context.Succeed(requirement);
            }
        }
    }
}

If the code doesn't reach context.Succeed(...) it will Fail anyway (401).

And then in your controllers you can use

 [Authorize(Policy = "Bearer", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

@Darren Lewis 2020-01-21 10:40:13

Why would you choose to perform your own validation of the token when the JwtBearer middleware already takes care of this? It also puts the correct content in the WWW-Authenticate response header for an auth/token validation/expiration failure. If you want access to the authentication pipeline there are specific events you can tap into on AddJwtBearer options (OnAuthenticationFailed, OnChallenge, OnMessageReceived and OnTokenValidated).

@blowdart 2015-07-16 21:57:54

I'm the asp.net security person. Firstly let me apologize that none of this is documented yet outside of the music store sample or unit tests, and it's all still being refined in terms of exposed APIs. Detailed documentation is here.

We don't want you writing custom authorize attributes. If you need to do that we've done something wrong. Instead, you should be writing authorization requirements.

Authorization acts upon Identities. Identities are created by authentication.

You say in comments you want to check a session ID in a header. Your session ID would be the basis for identity. If you wanted to use the Authorize attribute you'd write an authentication middleware to take that header and turn it into an authenticated ClaimsPrincipal. You would then check that inside an authorization requirement. Authorization requirements can be as complicated as you like, for example here's one that takes a date of birth claim on the current identity and will authorize if the user is over 18;

public class Over18Requirement : AuthorizationHandler<Over18Requirement>, IAuthorizationRequirement
{
        public override void Handle(AuthorizationHandlerContext context, Over18Requirement requirement)
        {
            if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
            {
                context.Fail();
                return;
            }

            var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value);
            int age = DateTime.Today.Year - dateOfBirth.Year;
            if (dateOfBirth > DateTime.Today.AddYears(-age))
            {
                age--;
            }

            if (age >= 18)
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
        }
    }
}

Then in your ConfigureServices() function you'd wire it up

services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", 
        policy => policy.Requirements.Add(new Authorization.Over18Requirement()));
});

And finally, apply it to a controller or action method with

[Authorize(Policy = "Over18")]

@jltrem 2015-07-17 13:10:19

Thanks for the example of making an AuthorizationHandler; I was able to get this working. Alternatively it seems I can accomplish what I need by subclassing ActionFilterAttribute and overriding OnActionExecuting, though this would be 'hacky' since it is outside the authentication framework.

@blowdart 2015-07-20 22:54:34

Yea, please don't head down the route of the action filter. We're hopeful that the new policy based system should be flexible enough for 99% of scenarios, and as a bonus, can be used in 3rd party frameworks as well. If you find cases it isn't working for you, email me at MSFT.

@Dbl 2015-07-23 19:30:20

@blowdart Is the way to handle authorize without a value for policy "options.AddPolicy(string.Empty, policy => policy.AddRequirements(new Test()));" ? I'm in the process of trying it out but right now i get an empty page + debugging does not work

@blowdart 2015-07-23 22:10:23

@AndreasMüller I'd be interesting why you want a blank policy name and what you expect to happen. It's not something we've tested, or coded for. This should be raised as an issue on github though.

@Dbl 2015-07-23 23:18:33

@blowdart well. basically i would like to have manual control over how authorization is verified even when it's just [Authorize] without any policy. That way it would be easy to allow scenarios entirely independent of ClaimsPrincipal being set (imagine single use request tokens for example). Are you going to raise the issue? I could try to but last time i had a reason to do so i got lost in githubs weird UI and gave up

@blowdart 2015-07-24 02:34:46

That still doesn't explain why you want a blank policy name. In any case you'll still need a principal. That's what authorization acts upon. As I still don't understand your issue I'd rather you open it. github.com/aspnet/security/issues and click the new issue button

@Dbl 2015-07-24 08:44:09

@blowdart alright. i'll do that when i get home. thanks.

@daredev 2015-08-04 17:57:47

@blowdart How would I go about accessing HttpContext/RouteData (params) in this requirement? It seems AuthorizationContext context.Resource is limited. Thanks!

@jltrem 2015-08-06 02:11:54

@daredev you can get to them in an AuthenticationHandler. See stackoverflow.com/a/31688792/571637

@Dustin Gamester 2015-08-12 16:14:52

@dareDev you can also cast context.Resouce to Microsoft.AspNet.Mvc.AuthorizationContext. Example: Microsoft.AspNet.Mvc.AuthorizationContext resource = (Microsoft.AspNet.Mvc.AuthorizationContext) context.Resource; string id = resource.RouteData.Values["id"].ToString();

@Tseng 2015-08-30 12:31:44

I wonder... how would one implement a fine grained access control with that? Let's say the ManageStore Requirement from Music Store sample. As it's in the sample, there is only an either "allow all or nothing" way to do it. Do we then have to create a new policy for every possible permutation? i.e. "Users/Read", "Users/Create", "Users/AssignRole", "Users/Delete" if we want fine-grained claims? Sounds like pretty much setup work to get it working and abundance of policies just to manage claims rather than a [ClaimsAutzorization("User", "Read", "Create", "Delete", "Assign")] attribute?

@blowdart 2015-12-07 21:22:28

We rewrote it because writing your own authorize attribute was riddled with failure and was broken.

@blowdart 2015-12-08 22:03:29

@Felype 2015-12-10 17:24:07

I have to comment that, all this is more complex than implementing a custom authorization method. I know how I want authorization to be done I could just go and write it in MVC 5, in MVC 6 they add a lot of "done" code that is actually more complex to understand than implementing the core "thing" itself. Gets me sitting in front of a page trying to figure something out instead of writing code right through, also a big pain for people who use RDBMS other than Microsoft's (or No-Sql).

@Felype 2015-12-11 10:57:07

I correct myself, all of the requests can be finely filtered and processed using Microsoft.AspNet.Mvc.Filters and Middleware classes, AspNet 5 does provide both built-in sophisticated tools and the possibility to fine tune web applications from the very root of request handling through a very nice and sophisticated pipeline system.

@Mariusz Jamro 2015-12-21 13:40:43

@blowdart Is there any naming convention for the policies?

@blowdart 2015-12-21 13:52:12

Nope, they're strings and you can name them as you like.

@Gerwald 2016-01-17 09:22:33

From my point of view, this doesnt solve all scenarios. Prior to MVC 6, I used a custom Authorize Attribute, to implement my own "Permission System". I could add the Authorize attribute to all actions, and pass one specific needed permission (as Enum-Value). The permission itself is was mapped to groups/users within the DB. So, I don't see a way to handle this with policies!?

@blowdart 2016-01-18 14:54:34

You'd replace your enum with a policy and requirement, and then DI your database into the handler.

@kwaclawek 2016-01-21 19:42:05

I agree, does not seem possible with Authorization attributes as one does not have access to the permission value in the attribute. @blowdart: Don't think it will work, the policy and handlers don't have access to the permission value encoded in the attribute.

@blowdart 2016-01-21 19:51:01

This is getting out of hand now. If you have questions then create them as questions, not comments.

@kwaclawek 2016-01-21 22:22:24

@blowdart: Wasn't a question.

@danludwig 2016-02-26 18:53:04

"We don't want you writing custom authorize attributes. If you need to do that we've done something wrong." How about returning a 403 Forbidden response when a user is authenticated but not authorized? 401 when a user is authenticated but not authorized is wrong.

@blowdart 2016-02-26 19:05:36

@danludwig that's what it does. Did you try it? Are you seeing something else? If so log a bug.

@Vi100 2016-04-08 09:10:38

blowdart: How would you code this AuthorizationHandler if the "Over18" requirement was of an arbitrary age for each specific action? Would I have to code an register a Requirement for every possible age? That's the case for @Gerwal, Tseng and mine. How can I pass a parameter to the concrete "instance" of the attribute???

@blowdart 2016-04-08 12:15:00

@Vi100 2016-04-08 14:07:57

That's the point. You can parameterise the Requirement, but not the Handler, nor the attribute... You can't do: [Authorize(MinAge = 21)] Of course Age is an example, I need to pass an enum with more than one hundred possible values to the handler. This way I'm forced to code a handler for every possible value of the requirement ("Over18", "Over19", "Over20", etc.)

@blowdart 2016-04-08 14:21:49

You parameterise the requirement, and you then configure it when you configure the policy. So you'd have multiple policies, but a single handler and a single requirement. Come RC2 you will be able to replace the policy provider, so you could stringify your requirements into, [Authorize(Policy="Over18")] then parse the policy name itself and return whatever policy you like.

@NathanAldenSr 2016-11-14 20:51:20

I, like many others in these comments, am very disappointed that using attributes for authorization has been so greatly neutered over what was possible in Web API 2. Sorry guys, but your "requirement" abstraction fails to cover any case where we could previously use attribute constructor parameters to inform an underlying authorization algorithm. It used to be brain-dead simple to do something like [CustomAuthorize(Operator.And, Permission.GetUser, Permission.ModifyUser)]. I could use a single custom attribute in an infinite number of ways simply by modifying the constructor parameters.

@NathanAldenSr 2016-11-14 20:54:18

I am also shocked that the self-proclaimed "Lead ASP.NET security guy" is actually suggesting to use magic strings (hacking the meaning of IAuthorizeData.Policy) and custom policy providers to overcome this blatant oversight, rather than addressing it within the framework. I thought we weren't supposed to be creating our own implementations? You've left several of us no choice except to re-implement authorization from scratch (again), and this time without even the benefit of Web API's old Authorize attribute. Now we have to do it on the action filter or middleware level.

@NathanAldenSr 2016-11-15 18:37:51

I posted a GitHub issue at github.com/aspnet/Mvc/issues/5532 that summarizes not only this issue but several other bad design choices I noticed while trying to migrate from Web API 2 to MVC 6.

@Shawn 2016-11-27 02:38:16

@NathanAldenSr Please see my answer, it will allow you to use custom attributes on your controllers and actions, and access them in the AuthorizationHandler. stackoverflow.com/a/40824351/436494

@Vi100 2016-11-28 10:21:53

@Shawn This is quite overengineered... I solved the same using a simple AuthorizationFilterAttribute wich receives a parameter. You don't need reflection for this, it seems even more artificious than the "official" solution (that I find quite poor).

@SHM 2016-12-02 13:45:14

@blowdart is there a way to access HTTP headers inside requirement?

@blowdart 2016-12-02 14:12:36

@SHM that would be better as a separate question

@Derek Greer 2016-12-27 03:58:57

I've added a comment to the issue spawned from this SO question: github.com/aspnet/Mvc/issues/5607#issuecomment-269266125

@maksymiuk 2017-02-07 00:47:05

I'm sorry that you feel you would do something wrong if we had to program something we need. I really needed this feature, and it was taken away unnecessarily. You don't need to make the program for us, we are programmers.

@Derek Greer 2017-04-12 13:05:34

I'd be interested in hearing more about how the attribute approach was broken and what issues can arise from their use that is being set forth as the reason you're recommending that no one should use that approach.

@Gayan 2017-08-23 03:40:45

@blowdart thanks for the explaination How to redirect to different view or route if failed this policy

@Sprague 2017-10-24 07:16:41

While it is the asp.net core team's choice do to this, I think it is not their business to tell developers that they can't handle their own implementations.

@Mohammed Noureldin 2017-11-11 11:44:40

Honestly, I like .Net Core very much! but I find the built-in authentication framework (Identity) not for me, it needs a rigid application and database structure.

@Jeremy Holovacs 2017-11-11 14:55:43

Policies are not sufficient at all. What if you need to check a claim to see if a user is a member of a certain group or organization, but the group or organization could be different based upon the request, policies fall completely flat. This new system offers little of value.

@blowdart 2017-11-11 16:45:15

I can't let that comment go from Jeremy, especially when the request is available during policy evaluation, so policies don't fall flat here at all, it supports exactly the example given about why they don't work.

@philreed 2017-12-11 15:38:35

@blowdart Are there some unit test examples for AuthorizationHandler? Specifically I am trying to test my handler behaves correctly if/when user belongs to AD Groups using context.User.IsInRole(roleName)

@Laurence 2018-02-01 15:32:49

@blowdart Does similar documentation exist for ASP.Net, or is this stuff Core only?

@Edgar Salazar 2018-02-02 18:23:55

@Felype you sound really accomodated. I was also in your position, and i've read to the point that i have the same security coding on aspnet core that i had on asp.net mvc 5. Technology will always progress, and we should always stay on or even forward to it as much as we can, or we'll fall behind. Love!

@Felype 2018-02-05 12:25:29

@EdgarSalazar yeah, I've figured, I'm using .net core since the early betas, now I know the story of what I'm using, its based on Ruby's Rack/Sinatra with a very strong Nodejs influence, I studied both of those superficially and It really added a lot, I'm also learning a lot in SPA and Rest and been flirting with the JavaScript development pipelines too.

@NightOwl888 2018-02-22 19:41:15

I'd be interested in hearing more about how the attribute approach was broken - @DerekGreer: I know this is an old question, but I believe you can find your answer in Passive Attributes. It is not a good design approach to make attributes contain behavior because they have constrained constructors, which makes it impossible to use the DI pattern to inject dependencies. Breaking the behavior out into individual components that can be registered at startup (and controlled by passive attributes) is a better approach.

@Derek Greer 2018-02-24 03:39:59

You can't use constructor injection with attributes, but you can do property injection. It's actually fairly easy with Autofac and I would image every other major container supports this. That aside, I highly doubt that was the aspect of the design that he's referring to when he says the design was broken.

@Dave3of5 2018-03-22 14:44:47

I've used this approach in my code but the biggest problem I see here is with the Authorize attribute as you need to type a string for the policy name. There is no compiler support / type checking if I make a mistake with the policy name. I'd like to be able to pass in an enum but again that requires a custom authorize attribute. Not really sure what the answer is here.

@Ankit Mori 2018-05-22 07:02:21

Hi Over18Requirement is just a class can you share code for same ?

@MrZarq 2018-06-05 11:15:51

@Dave3of5, you can use nameof(SomeEnum.SomeValue). It's a hack, but it works.

@arao6 2019-05-12 21:49:08

Oh what the heck, man... I am on a very tight deadline here and I don't have the time or energy to learn how to design middleware or claims or any of that... the point of a framework is to abstract this stuff out for us so we can simply focus on the core business logic (in my case, executing some code with the Firebase Admin SDK to ensure that a bearer token is valid). What should've been like 3 lines of code has turned into something a lot more.

@gburton 2019-08-15 09:40:02

Frankly this is a bit weak - a simple requirement such as checking permissions at runtime shouldn't take over a hundred lines of code.

@Dbl 2019-09-04 13:16:34

@blowdart despite all the shit you have received i like how authorization works now. Why am i saying this? It sucks when ppl only give feedback when they are frustrated instead of also when they are happy. At least i can just add one requirement and do attribute based specifics within the logic of that requirement now, which enables me to do the same stuff i used to be able to do earlier as well (apart from straight going the IAsyncAuthorizationFilter attribute way) - so yeah. Thanks.

@Herman Van Der Blom 2019-10-22 06:48:37

If you look at PHP etc. that software stays "simple". TomTom says: "making difficult things look easy is hard". Thats why they develop great Navigation software. Microsoft should take lessons from that. Microsoft software starts to become Messy and over complicated. It should be easy to implement Windows Authentication with using a custom user store. Lots of code, none with a good example. If I see the comments above, Microsoft should take notice of that.

@Tito 2019-11-14 16:58:31

Because I can't pass otther parameters appart from Policy on Authorization I am having this problem in here: stackoverflow.com/questions/58859199/…

@Hiren Desai 2020-01-02 14:20:31

@blowdart - How can I return HTTP 401 when the policy doesn't match. Right now for me, it throws invalid operation exception with InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.

@Derek Greer 2016-12-27 15:54:44

The approach recommended by the ASP.Net Core team is to use the new policy design which is fully documented here. The basic idea behind the new approach is to use the new [Authorize] attribute to designate a "policy" (e.g. [Authorize( Policy = "YouNeedToBe18ToDoThis")] where the policy is registered in the application's Startup.cs to execute some block of code (i.e. ensure the user has an age claim where the age is 18 or older).

The policy design is a great addition to the framework and the ASP.Net Security Core team should be commended for its introduction. That said, it isn't well-suited for all cases. The shortcoming of this approach is that it fails to provide a convenient solution for the most common need of simply asserting that a given controller or action requires a given claim type. In the case where an application may have hundreds of discrete permissions governing CRUD operations on individual REST resources ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder", etc.), the new approach either requires repetitive one-to-one mappings between a policy name and a claim name (e.g. options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));), or writing some code to perform these registrations at run time (e.g. read all claim types from a database and perform the aforementioned call in a loop). The problem with this approach for the majority of cases is that it's unnecessary overhead.

While the ASP.Net Core Security team recommends never creating your own solution, in some cases this may be the most prudent option with which to start.

The following is an implementation which uses the IAuthorizationFilter to provide a simple way to express a claim requirement for a given controller or action:

public class ClaimRequirementAttribute : TypeFilterAttribute
{
    public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
    {
        Arguments = new object[] {new Claim(claimType, claimValue) };
    }
}

public class ClaimRequirementFilter : IAuthorizationFilter
{
    readonly Claim _claim;

    public ClaimRequirementFilter(Claim claim)
    {
        _claim = claim;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
        if (!hasClaim)
        {
            context.Result = new ForbidResult();
        }
    }
}


[Route("api/resource")]
public class MyController : Controller
{
    [ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
    [HttpGet]
    public IActionResult GetResource()
    {
        return Ok();
    }
}

@Vi100 2017-03-10 12:00:54

This should be marked as the CORRECT ANSWER. Here you see how the people at Microsoft considers the developers feedback. I don't understand the reason they are so "closed minded" arround this, since it's a very common situation to have a miriad of different permissions, having to code one policy for each one is a complete overkill. I was looking for this for such a long time... (I already asked this question almost two years ago, when vNext was still a bet here: stackoverflow.com/questions/32181400/… but we're still stuck there)

@Mariano Peinador 2017-04-21 01:07:39

This is good stuff. We have authentication middleware on the Web API but grained security on the authorization permissions by role; so having to just throw in an attribute like: [MyAuthorize(MyClaimTypes.Permission, MyClaimValueTypes.Write, MyPermission.Employee)] looks very fine.

@ctv 2017-06-24 00:06:19

This is a great approach. I modified this for my situation to deal with Permissions (enum with flags attributes) instead.

@Jacob Phan 2017-09-08 04:03:50

@Derek Greer: This is the best answer. However, you are implement an ActionFilter which run after Authorize Action Filter. Is there anyway to implement and Authorize Action Filter?

@Derek Greer 2017-09-08 18:22:27

@JacobPhan You're correct, this would be better implemented using the IAuthorizationFilter interface. I've updated the code to reflect the changes.

@Sinaesthetic 2018-03-28 20:51:32

so new ForbidResult() doesn't work (causes exception/500) because it doesn't have an associated authorization scheme. What would I use for this case?

@Matty 2018-04-09 10:44:14

See also the discussion in this github issue: github.com/aspnet/Mvc/issues/5607, which is closed in favor of this issue: github.com/aspnet/Security/issues/1359

@jcmordan 2018-04-11 04:37:21

Hello Dereck, is there any way yo use the ClaimRequirementAttribute with and params string[] values list? something like that: public ClaimRequirementAttribute(string claimType, params string[] claimValues)

@Derek Greer 2018-04-16 23:19:35

Sure, just change the parameter types for both the attribute and the filter.

@JobaDiniz 2018-05-25 16:46:15

Is there a way to use such feature in WebApi 2?

@Derek Greer 2018-05-29 19:54:29

Create a new question and link it here and I'll provide an example.

@Odrai 2018-09-14 11:34:24

@DerekGreer Could you provide a Github test project, because in my solution the constructor of the ClaimRequirementFilter is called twice. The Claim object (parameter) properties are null during the first call and in the second they are filled. The OnAuthorization is only called once, using the null values

@Alex 75 2018-09-23 12:35:15

How can I inject a class instance (like a provider or repository) into the ClaimRequirementFilter ?

@Konrad 2018-12-04 13:58:57

@Alex75 use context.HttpContext.RequestServices, you can't really inject there.

@yogen darji 2019-01-02 14:00:59

Is there any way to use in c# WebApi?

@Derek Greer 2019-01-03 15:54:23

@yogendarji If you're trying to do this in older versions then you'd just extend AuthorizeAttribute. This answer is about how to do what you used to be able to do in older versions in .Net Core.

@Chaim Eliyah 2019-01-11 22:24:24

@yogendarji there's a classic WebApi example here

@Codemunkie 2019-03-23 20:47:04

Note that if your OnAuthorization implementation needs to await an async method, you should implement IAsyncAuthorizationFilter instead of IAuthorizationFilter otherwise your filter will execute synchronously and your controller action will execute regardless of the outcome of the filter.

@Derek Greer 2019-03-25 15:40:49

@Codemunkie It would certainly be advisable to implement the async version of the interface if the behavior being implemented requires the use of asynchronous methods, but your comment can really be distilled down to the advise of not invoking an asynchronous behavior whose results or side-effects need to be completed first before other operations.

@ilkerkaran 2019-07-30 20:37:32

this is an awesome answer! However, it does not work on SignalR Hubs and Hub methods

@shannon 2019-10-17 19:30:39

Note this approach may now exhibit a breaking change, if your implementation previously accounted for AllowAnonymous. See here: github.com/aspnet/Announcements/issues/391

@Derek Greer 2019-10-18 14:36:31

If I'm understanding this correctly, this would be relevant for anyone using this approach to place an claim attribute at the controller level to affect all actions where they desired the ability to override a given action with [AllowAnonymous]. The code presented here doesn't account for the AllowAnonymous attribute and so it would work the same before and after the breaking change, but that certainly would be a desirable and expected behavior of this attribute. Thanks for the link to the solution for accounting for this with version 3.

@StackOrder 2020-03-29 17:28:52

This is one of the best answers

@gius 2018-01-12 14:32:05

It seems that with ASP.NET Core 2, you can again inherit AuthorizeAttribute, you just need to also implement IAuthorizationFilter (or IAsyncAuthorizationFilter):

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    private readonly string _someFilterParameter;

    public CustomAuthorizeAttribute(string someFilterParameter)
    {
        _someFilterParameter = someFilterParameter;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;

        if (!user.Identity.IsAuthenticated)
        {
            // it isn't needed to set unauthorized result 
            // as the base class already requires the user to be authenticated
            // this also makes redirect to a login page work properly
            // context.Result = new UnauthorizedResult();
            return;
        }

        // you can also use registered services
        var someService = context.HttpContext.RequestServices.GetService<ISomeService>();

        var isAuthorized = someService.IsUserAuthorized(user.Identity.Name, _someFilterParameter);
        if (!isAuthorized)
        {
            context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
            return;
        }
    }
}

@MEMark 2018-08-29 10:44:45

So you can only use this to deny authorization, not grant it?

@gius 2018-08-30 11:05:58

@MEMark By granting, you mean overriding another authorization attribute?

@MEMark 2018-08-30 15:06:49

I guess you could put it like that. I mean effectively allowing access to the resource.

@gius 2018-09-03 10:08:05

AFAIK, access is allowed by default, so you need to explicitly deny it (e.g., by adding an AuthorizeAttribute). Check this question for more details: stackoverflow.com/questions/17272422/…

@Anatolyevich 2018-11-30 08:39:33

Also note, in suggested example one doesn't have to inherit from AuthorizeAttribute. You can inherit from Attribute and IAuthorizationFilter. This way you wouldn't get the following exception if some non-standard authentication mechanism is used: InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.

@ryancdotnet 2018-12-01 05:23:30

@Anatolyevich I still receive that error if I choose to do a context.Result = new ForbidResult();. But I'm liking this option so far.

@Codemunkie 2019-03-23 20:47:44

Note that if your OnAuthorization implementation needs to await an async method, you should implement IAsyncAuthorizationFilter instead of IAuthorizationFilter otherwise your filter will execute synchronously and your controller action will execute regardless of the outcome of the filter.

@bruno.almeida 2017-05-04 16:52:32

Based on Derek Greer GREAT answer, i did it with enums.

Here is an example of my code:

public enum PermissionItem
{
    User,
    Product,
    Contact,
    Review,
    Client
}

public enum PermissionAction
{
    Read,
    Create,
}


public class AuthorizeAttribute : TypeFilterAttribute
{
    public AuthorizeAttribute(PermissionItem item, PermissionAction action)
    : base(typeof(AuthorizeActionFilter))
    {
        Arguments = new object[] { item, action };
    }
}

public class AuthorizeActionFilter : IAuthorizationFilter
{
    private readonly PermissionItem _item;
    private readonly PermissionAction _action;
    public AuthorizeActionFilter(PermissionItem item, PermissionAction action)
    {
        _item = item;
        _action = action;
    }
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        bool isAuthorized = MumboJumboFunction(context.HttpContext.User, _item, _action); // :)

        if (!isAuthorized)
        {
            context.Result = new ForbidResult();
        }
    }
}

public class UserController : BaseController
{
    private readonly DbContext _context;

    public UserController( DbContext context) :
        base()
    {
        _logger = logger;
    }

    [Authorize(PermissionItem.User, PermissionAction.Read)]
    public async Task<IActionResult> Index()
    {
        return View(await _context.User.ToListAsync());
    }
}

@Anton Swanevelder 2018-03-29 08:13:00

Thanks for this. I created this post with a slightly different implementation and a request for validation stackoverflow.com/questions/49551047/…

@Marek Urbanowicz 2019-09-07 05:27:20

MumboJumboFunction <3

@Kévin Chalet 2015-07-16 21:04:48

What is the current approach to make a custom AuthorizeAttribute

Easy: don't create your own AuthorizeAttribute.

For pure authorization scenarios (like restricting access to specific users only), the recommended approach is to use the new authorization block: https://github.com/aspnet/MusicStore/blob/1c0aeb08bb1ebd846726232226279bbe001782e1/samples/MusicStore/Startup.cs#L84-L92

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<AuthorizationOptions>(options =>
        {
            options.AddPolicy("ManageStore", policy => policy.RequireClaim("Action", "ManageStore"));
        });
    }
}

public class StoreController : Controller
{
    [Authorize(Policy = "ManageStore"), HttpGet]
    public async Task<IActionResult> Manage() { ... }
}

For authentication, it's best handled at the middleware level.

What are you trying to achieve exactly?

@jltrem 2015-07-16 21:09:17

I am receiving a session ID in the Header Authorization. From that ID I'll know whether a particular action is valid.

@Kévin Chalet 2015-07-16 21:12:05

Then that's not an authorization concern. I guess your "session ID" is actually a token containing the identity of the caller: this should definitely be done at the middleware level.

@jltrem 2015-07-16 21:18:27

It isn't authentication (establishing who the user is) but it is authorization (determining if a user should have access to a resource). So where are you suggesting I look to solve this?

@Kévin Chalet 2015-07-16 21:19:49

Curious: how are you creating your session identifiers?

@bopapa_1979 2015-07-16 21:21:10

@jltrem, agreed, what you are talking about is authorization, not authentication.

@jltrem 2015-07-16 21:22:13

@Pinpoint I'm not creating them. I'm just the guy in the middle providing access to another system.

@Kévin Chalet 2015-07-16 21:22:45

@EricBurcham a session identifier is definitely a way to identity (= authenticate) a user, I'm afraid.

@Kévin Chalet 2015-07-16 21:23:53

@jltrem but you're extracting some information from the identifier to determine who's the caller, right?

@jltrem 2015-07-16 21:32:09

@Pinpoint I am not. I query another system for that info. That system authenticates (determines the user) and authorizes (tells me what that user can access). Right now I have it hacked to work by calling a method in each controller action to have the other system verify the session. I'd like to have this automatically happen via an attribute.

@Kévin Chalet 2015-07-16 21:38:33

I am not. I query another system for that info... well, you're asking an external provider to authenticate your users, but that's exactly the same thing, I'm afraid. You should consider creating a new middleware for the external authentication part (it would query your remote system and retrieve user's identity before creating a new ClaimsPrincipal containing the roles or the actions the user is allowed to query) and adding a new authorization policy to ensure the caller has a role/claim allowing him/her to make the request.

@Kévin Chalet 2017-10-31 14:06:24

@BlakeNiemyjski fixed. Thanks.

@Konrad 2018-12-04 11:54:19

"ManageStore" string is repeating too much.

@Konrad Viltersten 2019-01-29 23:15:38

The proposed solution executes a block of code when a controller attributed with policy specified is reached. However, based on the methods/properties provided by intellisense, I infer that it's only about how to perform the actual authorization. I wonder how I could do about doing something else as the policy is reached - e.g. how could I redirect the user to a log in page? Is it possible without overriding the attribute?

@Shawn 2016-11-26 23:46:33

You can create your own AuthorizationHandler that will find custom attributes on your Controllers and Actions, and pass them to the HandleRequirementAsync method.

public abstract class AttributeAuthorizationHandler<TRequirement, TAttribute> : AuthorizationHandler<TRequirement> where TRequirement : IAuthorizationRequirement where TAttribute : Attribute
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement)
    {
        var attributes = new List<TAttribute>();

        var action = (context.Resource as AuthorizationFilterContext)?.ActionDescriptor as ControllerActionDescriptor;
        if (action != null)
        {
            attributes.AddRange(GetAttributes(action.ControllerTypeInfo.UnderlyingSystemType));
            attributes.AddRange(GetAttributes(action.MethodInfo));
        }

        return HandleRequirementAsync(context, requirement, attributes);
    }

    protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, IEnumerable<TAttribute> attributes);

    private static IEnumerable<TAttribute> GetAttributes(MemberInfo memberInfo)
    {
        return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
    }
}

Then you can use it for any custom attributes you need on your controllers or actions. For example to add permission requirements. Just create your custom attribute.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class PermissionAttribute : AuthorizeAttribute
{
    public string Name { get; }

    public PermissionAttribute(string name) : base("Permission")
    {
        Name = name;
    }
}

Then create a Requirement to add to your Policy

public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
    //Add any custom requirement properties if you have them
}

Then create the AuthorizationHandler for your custom attribute, inheriting the AttributeAuthorizationHandler that we created earlier. It will be passed an IEnumerable for all your custom attributes in the HandleRequirementsAsync method, accumulated from your Controller and Action.

public class PermissionAuthorizationHandler : AttributeAuthorizationHandler<PermissionAuthorizationRequirement, PermissionAttribute>
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement, IEnumerable<PermissionAttribute> attributes)
    {
        foreach (var permissionAttribute in attributes)
        {
            if (!await AuthorizeAsync(context.User, permissionAttribute.Name))
            {
                return;
            }
        }

        context.Succeed(requirement);
    }

    private Task<bool> AuthorizeAsync(ClaimsPrincipal user, string permission)
    {
        //Implement your custom user permission logic here
    }
}

And finally, in your Startup.cs ConfigureServices method, add your custom AuthorizationHandler to the services, and add your Policy.

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

        services.AddAuthorization(options =>
        {
            options.AddPolicy("Permission", policyBuilder =>
            {
                policyBuilder.Requirements.Add(new PermissionAuthorizationRequirement());
            });
        });

Now you can simply decorate your Controllers and Actions with your custom attribute.

[Permission("AccessCustomers")]
public class CustomersController
{
    [Permission("AddCustomer")]
    IActionResult AddCustomer([FromBody] Customer customer)
    {
        //Add customer
    }
}

@NathanAldenSr 2016-11-28 02:33:35

I will take a look at this ASAP.

@Vi100 2016-11-28 10:20:28

This is quite overengineered... I solved the same using a simple AuthorizationFilterAttribute wich receives a parameter. You don't need reflection for this, it seems even more artificious than the "official" solution (that I find quite poor).

@Shawn 2016-11-28 21:13:06

@Vi100 I couldn't find much information on AuthorizationFilters in ASP.NET Core. The official documentation page says they are currently working on this topic. docs.microsoft.com/en-us/aspnet/core/security/authorization/‌​…

@Shawn 2016-11-29 02:55:27

@Vi100 Can you please share your solution, if there is a simpler way to achieve this I would love to know.

@Kizzim 2016-12-04 20:47:03

@Vi100 I agree, if you have a solution, please share it. Shawn is right there is pretty much zero documentation on Authorization Filters out there right now, only thing we see is 'working on it'

@teatime 2017-06-21 12:54:15

I actually like this solution, it leverages the new policy system and combines Attributes to provide a pretty clean solution. I use a global Authorize attribute to ensure the user is logged in, then apply a permission policy where required.

@teatime 2017-06-21 12:54:45

One thing to note the use of UnderlyingSystemType above does not compile, but removing it seems to work.

@pberggreen 2017-11-20 08:09:33

I like this solution. It is exactly what I was looking for. However if @Vi100 or anybody else has found a solution that doesn't require reflection, I would love to see it.

@Radu 2018-06-05 06:27:35

I found this topic you can take a look at its quite clean stackoverflow.com/questions/41293420/…. Hope it helps

@MrZarq 2018-06-06 08:13:57

This works! It is quite absurd that this is the only way to implement some sort of fine-grained access control in ASP.Net Core.

Related Questions

Sponsored Content

43 Answered Questions

[SOLVED] How do you convert a byte array to a hexadecimal string, and vice versa?

  • 2008-11-22 10:03:13
  • alextansc
  • 838147 View
  • 1352 Score
  • 43 Answer
  • Tags:   c# arrays hex

43 Answered Questions

[SOLVED] How do I create an Excel (.XLS and .XLSX) file in C# without installing Microsoft Office?

  • 2008-09-29 22:30:28
  • mistrmark
  • 1078476 View
  • 1862 Score
  • 43 Answer
  • Tags:   c# .net excel file-io

36 Answered Questions

[SOLVED] How do you create a dropdownlist from an enum in ASP.NET MVC?

2 Answered Questions

[SOLVED] ASP.NET Core 2.0 authentication middleware

2 Answered Questions

1 Answered Questions

[SOLVED] Custom authorization attributes in ASP.NET Core

1 Answered Questions

[SOLVED] Custom AuthorizeAttribute Failing to Authorize

1 Answered Questions

[SOLVED] Authorizing a user depending on the action name

Sponsored Content