Skip to content

Commit

Permalink
Apply latest ASP.NET Core/C# standards
Browse files Browse the repository at this point in the history
  • Loading branch information
Roland Guijt committed Jun 15, 2024
1 parent c564e14 commit ac5defd
Showing 1 changed file with 37 additions and 43 deletions.
80 changes: 37 additions & 43 deletions IdentityServer/v7/docs/content/apis/aspnetcore/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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:
Expand Down Expand Up @@ -78,60 +72,60 @@ 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;

/// <summary>
/// Logic for normalizing scope claims to separate claim types
/// </summary>
public static class ScopeConverter
{
/// <summary>
/// Logic for normalizing scope claims to separate claim types
/// </summary>
public static class ScopeConverter
/// <param name="principal"></param>
/// <returns></returns>
public static ClaimsPrincipal NormalizeScopeClaims(this ClaimsPrincipal principal)
{
/// <summary>
/// Logic for normalizing scope claims to separate claim types
/// </summary>
/// <param name="principal"></param>
/// <returns></returns>
public static ClaimsPrincipal NormalizeScopeClaims(this ClaimsPrincipal principal)
var identities = new List<ClaimsIdentity>();

foreach (var id in principal.Identities)
{
var identities = new List<ClaimsIdentity>();
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
{
identity.AddClaim(claim);
}
}

identities.Add(identity);
else
{
identity.AddClaim(claim);
}
}
return new ClaimsPrincipal(identities);

identities.Add(identity);
}

return new ClaimsPrincipal(identities);
}
}
```
Expand Down

0 comments on commit ac5defd

Please sign in to comment.