简体   繁体   中英

C# Web API with Scope Specific Routes

I am utilizing IdentityServer4 as the identity provider and my C# Web API (for Users) is protected via:

services.AddAuthentication(Authentication.Bearer)
  .AddJwtBearer(Authentication.Bearer, options =>
  {
    options.Authority = Configuration.GetSection("IdentityServer:Authority").Value;
    options.Audience = Scopes.UsersService;
  });

and [Authorize] on the Controllers. So, anyone who wants to call the routes in the UsersService needs a verified token with "usersservice" as a scope.

I would like to open up a select few routes via scope. So, if the route caller has the scope "usersservice", they would still have access to all routes. But, if they have the scope "usersservice.read", they would only have access to routes that I designate for read access. How can I do this?

Solution :

Alpha75 listed a bunch of options as to how to resolve this. I ended up going with the AddPolicy route. This is how I implemented it for my usersservice. I removed the "userservice" scope and added two scopes "usersservice.all" and "usersservice.read", and not simply rely on the audience.

In Startup.cs after services.AddAuthentication(Authentication.Bearer) :

services.AddAuthorization(options =>
  options.AddPolicy("all", policy =>
    policy.RequireClaim("scope", "usersservice.all")
    )
  );

services.AddAuthorization(options =>
  options.AddPolicy("read", policy => 
    policy.RequireClaim("scope", "usersservice.all", "usersservice.read")
    )
  );

Then on the read actions, I have [Authorize(Policy = "read")] and on all other actions, I have [Authorize(Policy = "all")] . Note : "usersservice.all" is in the "read" policy so any caller with the all scope also has access to the read actions.

Api

One thing is the Api Resource and another thing are the Api Scopes of the Api Resource. In the handler of your API you must to configure your Api Resource.

options.Audience = "usersservice";

And to protect by scope you can use several approaches :

  • Protect action method:

     [HttpGet] [RequiredScope("usersservice.write")] public IEnumerable<TodoItem> Get() { // Do the work and return the result. // ... }
  • Protect controller:

     [Authorize] [RequiredScope("usersservice.read")] public class TodoListController : Controller { ... }
  • Conditionally by using extension method VerifyUserHasAnyAcceptedScope

     [HttpGet] public IEnumerable<TodoItem> Get() { HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); // Do the work and return the result. // ... }
  • Based on policies ( examples in Duende documentation ):

     // 1. In ConfigureServices services.AddAuthorization(options => { options.AddPolicy("read_access", policy => policy.RequirementClaim("scope", "usersservice.read"); }); // 2.a. In the Controller declaratively [Authorize("read_access")] public async Task<IActionResult> Get() { // rest omitted } // 2.b. Or In the Controller imperatively public class DataController : ControllerBase { IAuthorizationService _authz; public DataController(IAuthorizationService authz) { _authz = authz; } public async Task<IActionResult> Get() { var allowed = _authz.CheckAccess(User, "read_access"); // rest omitted } }
  • Based on policies but using custom requirements . This is more flexible than others but maybe is not necessary if you only want to check the scope.

IdentityServer configuration

Depending on the IdentityServer4 version you'll have different model for the relation ApiResource <-> ApiScope:

...starting with v4, scopes have their own definition and can optionally be referenced by resources. Before v4, scopes were always contained within a resource.

IdentityServer4 docs

If you use v4, you could have a configuration like this (you'll probably have a database with your configuration):

public static IEnumerable<ApiScope> GetApiScopes()
{
    return new List<ApiScope>
    {
        // userservice API specific scopes
        new ApiScope(name: "usersservice.read",   displayName: "Reads user data"),
        new ApiScope(name: "usersservice.write",    displayName: "Writes user data"),

        // another API specific scopes
        new ApiScope(name: "another.read",    displayName: "Reads another data"),            

        // shared scope
        new ApiScope(name: "manage", displayName: "Provides administrative access to user and another data")
    };
}

public static readonly IEnumerable<ApiResource> GetApiResources()
{
    return new List<ApiResource>
    {
        new ApiResource("usersservice", "Users Service API")
        {
            Scopes = { "usersservice.read", "usersservice.write", "manage" }
        },

        new ApiResource("anotherservice", "Another API")
        {
            Scopes = { "another.read", "manage" }
        }
    };
}

Note that with this model you can "share" scopes between resources, although it is optional. In the example, the scope "manage" allow access to both services. This wasn't possible before v4.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM