Search code examples
c#.net-corejwtauthorizationauthorize

.NET [Authorize] - Use JWT Body Path Other than "Role"


We have standard .NET Core [Authorize(Roles = "Foo")] authorization attributes.

They are set up in Startup.cs to check roles via a JWT in the Authorize header in the standard, out-of-the-box way, e.g.:

.AddJwtBearer(jwtBearerOptions => {...}

Our current tokens look like below and work perfectly:

{
  "role": "Foo",
  "exp": 1937249412,
}

Our new tokens will look like the tokens below. We have NO control over the incoming tokens (but do have the symmetric key):

{
  "www.bar.com/role": "Foo",
  "exp": 1937249412,
}

Core question: Is there any way to change [Authorize] to look for the token in www.bar.com/role and not Role in the JWT body, which it does by default?

P.S. I know I can add middleware to write a Role to the token and re-sign the token. I also know that I can write [CustomAuthorize] attributes or use policy-based authentication. It just feels like there should be an easy way to do something this simple without having to mess with incoming requests OR replacing the [Authorize] attribute in the whole app.


Solution

  • First solution that comes to mind

    You could play it via policies and use roles in the background. Here is an example. In your controller:

    [Authorize(Policy = "Foo")]
    public Whatever() {}
    

    And then you register your policy like this:

    services.AddAuthorization(options =>
    {
        options.AddPolicy("Foo", policy => policy.RequireClaim("www.bar.com/role", new[] { "Foo" }));
    }
    

    I.e. your "Foo" policy requires a user to have the value "Foo" for the claim "www.bar.com/role".

    Digging deeper into the source

    In fact if you add the Authorize attribute with a role, not much more happens in the background as in the Policy example above. .net creates an authorization policy with a RolesAuthorizationRequirement, and this requirement checks the roles by calling context.User.IsInRole, which, in turn calls HasClaim(_identities[i].RoleClaimType, role) on the ClaimsIdentity. RoleClaimType is set in .net ClaimsIdentity to "roles/" by default.

    So another way would probably be intercepting the creation of the ClaimsIdentity and calling it with another constructor which has a "roleType" parameter, which allows overriding of the default role claim name.

    A probably better solution

    So I've looked for the ClaimsIdentity and found this docu. It seems that you can get to it via token validation parameters:

    services.AddAuthentication()
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            RoleClaimType = "www.bar.com/role"
        }
    })