asp.net core Web API: use Roles and RequiredScope concurrent

Viewed 337

I have a WebAPI with OAuth2 (Microsoft Identity) and I need to support 'Client Credential Flow' (for non-interactive driven clients, with Roles) as well as 'Autentication Credential Flow' (for human-interactive clients, with Scopes).

How can I make it run with either 'MyRole' or with 'MyScope'? ... when the controller basically looks like this:

[Authorize(Roles = "MyRole")]
[RequiredScope("MyScope")]
public class MyController : ControllerBase
{
}

I work with .NET 6

2 Answers

Thanks to Rena, I found solution.

  1. Create a requirement and an authorization requirement handler:

     public class ClaimsScopeOrRoleRequirement : IAuthorizationRequirement
     {
     }
    
     public class ClaimsScopeOrRoleRequirementHandler : AuthorizationHandler<ClaimsScopeOrRoleRequirement>
     {
         protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClaimsScopeOrRoleRequirement requirement)
         {
             var canPass = context.User.IsInRole("MyRole"))
                 || context.User.HasClaim(y =>
                     (y.Type == ClaimConstants.Scope || y.Type == ClaimConstants.Scp)
                         && y.Value == "MyScope"));
    
             if (!canPass)
             {
                 context.Fail(new AuthorizationFailureReason(this, $"User neither claims required scope nor role."));
             }
             else
             {
                 context.Succeed(requirement);
             }
    
             return Task.CompletedTask;
         }
     }
    
  2. Add a new authorization policy and a dependency singleton for the authorization requirement handler to the services (in Program.cs):

    builder.Services.AddAuthorization(options => { options.AddPolicy("MyPolicy", policy => policy.Requirements.Add(new ClaimsScopeOrRoleRequirement());

    });

    builder.Services.AddSingleton<IAuthorizationHandler, ClaimsScopeOrRoleRequirementHandler>();

  3. Decorate the controller with the Authorize attribute with the added policy

    [Authorize("MyPolicy")] public class MyController : ControllerBase { }

So it works.

(sorry, code formatting did not apply for all of the code parts in this post ...)

Related