Search code examples
asp.netasp.net-coreasp.net-web-apiauthorizationidentityserver4

How to add an authorization policy with multiple scopes?


options.AddPolicy("Account", policy => policy.RequireClaim(JwtClaimTypes.Scope, "account"));
options.AddPolicy("AccountWrite", policy => policy.RequireClaim(JwtClaimTypes.Scope, "account.write"));
options.AddPolicy("AccountRead", policy => policy.RequireClaim(JwtClaimTypes.Scope, "account.read"));

How do you add an OR to a policy scope?

  • "Account" policy when the scope is "account" (read, write and delete)
  • "AccountWrite" policy when the scope is either "account" OR "account.write" (read and write)
  • "AccountRead" policy when the scope is "account.read" (read only)

Solution

  • To answer your question, you can add multiple arguments to the RequireClaim statement. As documented:

    RequireClaim(String, String[])

    claimType String

    The claim type required.

    allowedValues IEnumerable

    Values the claim must process one or more of for evaluation to succeed.

    In your case something like:

    options.AddPolicy("Account", policy => 
        policy.RequireClaim(JwtClaimTypes.Scope, "account", "account.read", "account.write", "account.delete"));
    options.AddPolicy("AccountWrite", policy => 
        policy.RequireClaim(JwtClaimTypes.Scope, "account", "account.write"));
    options.AddPolicy("AccountRead", policy => 
        policy.RequireClaim(JwtClaimTypes.Scope, "account", "account.read"));
    

    But this is a bit odd for scopes.

    Having a scope means that a client is authorized to access the resource, regardless which user uses the client.

    A scope marks a part of the resource that does a specific task, in other words, has certain functionality.

    Assuming a CRUD AccountController:

    1. You can authorize a client to access the entire controller, use the account scope in that case on top of the controller.

    2. You can authorize the client per method, e.g. account.read the Index method, account.write the Create and Update methods and account.delete the Delete method. It's not likely that scopes can be mixed due to the difference in functionality.

    Both can be fine, because it's the user that needs to be authorized to use the resource. The client takes the user to the resource and does the request on behalf of the user. But it makes no sense to combine both.

    What would be the appropriate design for you?

    Suppose you have an admin app, where the user is allowed to manage the account. The customer has its own app and wants to access the resource using that app, but you don't want to allow full management from that app.

    In that case the client app from the customer should be allowed to request the account.read scope only. Because, if the user is allowed to manage the account, then you want to make sure that this is only possible using your app.

    So you can 'normalize' the policies.

    For 1.

    options.AddPolicy("Account", policy => 
        policy.RequireClaim(JwtClaimTypes.Scope, "account"));
    

    and for 2.

    options.AddPolicy("AccountDelete", policy => 
        policy.RequireClaim(JwtClaimTypes.Scope, "account.delete"));
    options.AddPolicy("AccountWrite", policy => 
        policy.RequireClaim(JwtClaimTypes.Scope, "account.write"));
    options.AddPolicy("AccountRead", policy => 
        policy.RequireClaim(JwtClaimTypes.Scope, "account.read"));
    

    Come to think of it, based on the customer app as mentioned above, there is a third option:

    1. You can authorize the client per method, e.g. AccountRead the Index method and Account for the other methods. Where AccountRead must include the account scope.

    The policies would then look like:

    // policy to allow client access to write and delete functionality
    options.AddPolicy("Account", policy => 
        policy.RequireClaim(JwtClaimTypes.Scope, "account"));
    // policy to allow client access to read functionality only
    options.AddPolicy("AccountRead", policy => 
        policy.RequireClaim(JwtClaimTypes.Scope, "account", "account.read"));
    

    The admin app only has to request the account scope, while the customer app can request the account.read scope.

    Please note that the last part does not answer your original question but may answer the questions raised in the comments.