diff --git a/IdentityServer/v7/docs/content/apis/aspnetcore/authorization.md b/IdentityServer/v7/docs/content/apis/aspnetcore/authorization.md index 592eb1ad..6403e201 100644 --- a/IdentityServer/v7/docs/content/apis/aspnetcore/authorization.md +++ b/IdentityServer/v7/docs/content/apis/aspnetcore/authorization.md @@ -4,9 +4,9 @@ date: 2020-09-10T08:22:12+02:00 weight: 30 --- -The access token will include additional claims that can be used for authorization, e.g. the *scope* claim will reflect the scope the client requested (and was granted) during the token request. +The access token will include additional claims that can be used for authorization, e.g. the _scope_ claim will reflect the scope the client requested (and was granted) during the token request. -In ASP.NET core, the contents of the JWT payload get transformed into claims and packaged up in a *ClaimsPrincipal*. So you can always write custom validation or authorization logic in C#: +In ASP.NET core, the contents of the JWT payload get transformed into claims and packaged up in a _ClaimsPrincipal_. So you can always write custom validation or authorization logic in C#: ```cs public IActionResult Get() @@ -22,23 +22,17 @@ For better encapsulation and re-use, consider using the ASP.NET Core [authorizat With this approach, you would first turn the claim requirement(s) into a named policy: ```cs -public void ConfigureServices(IServiceCollection services) +builder.Services.AddAuthorization(options => { - services.AddAuthorization(options => - { - options.AddPolicy("read_access", policy => - policy.RequireClaim("scope", "read"); - }); -} + options.AddPolicy("read_access", policy => + policy.RequireClaim("scope", "read"); +}); ``` ..and then enforce it, e.g. using the routing table: ```cs - app.UseEndpoints(endpoints => - { - endpoints.MapControllers().RequireAuthorization("read_access"); - }); +app.MapControllers().RequireAuthorization("read_access"); ``` ...or imperatively inside the controller: @@ -78,47 +72,43 @@ public class DataController : ControllerBase ``` #### Scope claim format -Historically, Duende IdentityServer emitted the *scope* claims as an array in the JWT. This works very well with the .NET deserialization logic, which turns every array item into a separate claim of type *scope*. -The newer *JWT Profile for OAuth* [spec]({{< ref "/overview/specs" >}}) mandates that the scope claim is a single space delimited string. You can switch the format by setting the *EmitScopesAsSpaceDelimitedStringInJwt* on the [options]({{< ref "/reference/options" >}}). But this means that the code consuming access tokens might need to be adjusted. The following code can do a conversion to the *multiple claims* format that .NET prefers: +Historically, Duende IdentityServer emitted the _scope_ claims as an array in the JWT. This works very well with the .NET deserialization logic, which turns every array item into a separate claim of type _scope_. + +The newer _JWT Profile for OAuth_ [spec]({{< ref "/overview/specs" >}}) mandates that the scope claim is a single space delimited string. You can switch the format by setting the _EmitScopesAsSpaceDelimitedStringInJwt_ on the [options]({{< ref "/reference/options" >}}). But this means that the code consuming access tokens might need to be adjusted. The following code can do a conversion to the _multiple claims_ format that .NET prefers: ```cs -namespace IdentityModel.AspNetCore.AccessTokenValidation +namespace IdentityModel.AspNetCore.AccessTokenValidation; + +/// +/// Logic for normalizing scope claims to separate claim types +/// +public static class ScopeConverter { /// /// Logic for normalizing scope claims to separate claim types /// - public static class ScopeConverter + /// + /// + public static ClaimsPrincipal NormalizeScopeClaims(this ClaimsPrincipal principal) { - /// - /// Logic for normalizing scope claims to separate claim types - /// - /// - /// - public static ClaimsPrincipal NormalizeScopeClaims(this ClaimsPrincipal principal) + var identities = new List(); + + foreach (var id in principal.Identities) { - var identities = new List(); + var identity = new ClaimsIdentity(id.AuthenticationType, id.NameClaimType, id.RoleClaimType); - foreach (var id in principal.Identities) + foreach (var claim in id.Claims) { - var identity = new ClaimsIdentity(id.AuthenticationType, id.NameClaimType, id.RoleClaimType); - - foreach (var claim in id.Claims) + if (claim.Type == "scope") { - if (claim.Type == "scope") + if (claim.Value.Contains(' ')) { - if (claim.Value.Contains(' ')) - { - var scopes = claim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries); + var scopes = claim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries); - foreach (var scope in scopes) - { - identity.AddClaim(new Claim("scope", scope, claim.ValueType, claim.Issuer)); - } - } - else + foreach (var scope in scopes) { - identity.AddClaim(claim); + identity.AddClaim(new Claim("scope", scope, claim.ValueType, claim.Issuer)); } } else @@ -126,12 +116,16 @@ namespace IdentityModel.AspNetCore.AccessTokenValidation identity.AddClaim(claim); } } - - identities.Add(identity); + else + { + identity.AddClaim(claim); + } } - - return new ClaimsPrincipal(identities); + + identities.Add(identity); } + + return new ClaimsPrincipal(identities); } } ```