diff --git a/IdentityServer/v6/docs/content/ui/login/dynamicproviders.md b/IdentityServer/v6/docs/content/ui/login/dynamicproviders.md index 5f6977f0..d8139a02 100644 --- a/IdentityServer/v6/docs/content/ui/login/dynamicproviders.md +++ b/IdentityServer/v6/docs/content/ui/login/dynamicproviders.md @@ -5,11 +5,11 @@ weight: 65 ## Dynamic Identity Providers -Normally authentication handlers for external providers are added into your IdentityServer using *AddAuthentication()* and *AddOpenIdConnect()*. This is fine for a handful of schemes, but the authentication handler architecture in ASP.NET Core was not designed for dozens or more statically registered in the DI system. At some point you will incur a performance penalty for having too many. Also, as you need to add or change this configuration you will need to re-compile and re-run your startup code for those changes to take effect. +Normally authentication handlers for external providers are added into your IdentityServer using _AddAuthentication()_ and _AddOpenIdConnect()_. This is fine for a handful of schemes, but the authentication handler architecture in ASP.NET Core was not designed for dozens or more statically registered in the DI system. At some point you will incur a performance penalty for having too many. Also, as you need to add or change this configuration you will need to re-compile and re-run your startup code for those changes to take effect. Duende IdentityServer provides support for dynamic configuration of OpenID Connect providers loaded from a store. This is designed to address the performance concern as well as allowing changes to the configuration to a running server. -Support for Dynamic Identity Providers is included in [IdentityServer](https://duendesoftware.com/products/identityserver) Enterprise Edition. +Support for Dynamic Identity Providers is included in [IdentityServer](https://duendesoftware.com/products/identityserver) Enterprise Edition. ### Listing and displaying the dynamic providers on the login page @@ -32,12 +32,11 @@ The [identity provider store]({{}}) can be us These results can then be used to populate the list of options presented to the user on the login page. -This API is deliberately separate than the *IAuthenticationSchemeProvider* provided by ASP.NET Core, which returns the list of statically configured providers (from *Startup.cs*). +This API is deliberately separate than the _IAuthenticationSchemeProvider_ provided by ASP.NET Core, which returns the list of statically configured providers (from _Startup.cs_). This allows the developer to have more control over the customization on the login page (e.g. there might be hundreds or thousands on dynamic providers, and therefore you would not want them displayed on the login page, but you might have a few social providers statically configured that you would want to display). Here is an example of how the [IdentityServer Quickstart UI](https://github.com/DuendeSoftware/IdentityServer.Quickstart.UI/blob/main/Quickstart/Account/AccountController.cs#L265-L282) uses both interfaces to then present a merged and unified list to the end user: - ```cs var schemes = await _schemeProvider.GetAllSchemesAsync(); @@ -66,18 +65,17 @@ To use the dynamic providers feature an [identity provider store]({{}}) then this is implemented for you. {{% notice note %}} -Like other configuration data in IdentityServer, by default the dynamic provider configuration is loaded from the store on every request unless caching is enabled. +Like other configuration data in IdentityServer, by default the dynamic provider configuration is loaded from the store on every request unless caching is enabled. If you use a custom store, there is an [extension method to enable caching]({{}}). If you use the EF stores, there is general helper [to enable caching for all configuration data]({{}}). {{% /notice %}} -The configuration data for the OIDC provider is used to assign the configuration on the ASP.NET Core [OpenID Connect Options](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions) class, much like you would if you were to statically configure the options when using *AddOpenIdConnect()*. +The configuration data for the OIDC provider is used to assign the configuration on the ASP.NET Core [OpenID Connect Options](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions) class, much like you would if you were to statically configure the options when using _AddOpenIdConnect()_. The [identity provider model documentation]({{}}) provides details for the model properties and how they are mapped to the options. - #### Customizing OpenIdConnectOptions -If it is needed to further customize the *OpenIdConnectOptions*, you can register in the DI system an instance of *IConfigureNamedOptions\*. For example: +If it is needed to further customize the _OpenIdConnectOptions_, you can register in the DI system an instance of _IConfigureNamedOptions\_. For example: ```cs public class CustomConfig : IConfigureNamedOptions @@ -107,14 +105,15 @@ And to register this in the DI system: #### Accessing OidcProvider data in IConfigureNamedOptions -If your customization of the *OpenIdConnectOptions* requires per-provider data that you are storing on the *OidcProvider*, then we provide an abstraction for the *IConfigureNamedOptions\*. -This abstraction requires your code to derive from *ConfigureAuthenticationOptions\* (rather than *IConfigureNamedOptions\*). +If your customization of the _OpenIdConnectOptions_ requires per-provider data that you are storing on the _OidcProvider_, then we provide an abstraction for the _IConfigureNamedOptions\_. +This abstraction requires your code to derive from _ConfigureAuthenticationOptions\_ (rather than _IConfigureNamedOptions\_). For example: ```cs class CustomOidcConfigureOptions : ConfigureAuthenticationOptions { - public CustomOidcConfigureOptions(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor) + public CustomOidcConfigureOptions(IHttpContextAccessor httpContextAccessor, + ILogger logger) : base(httpContextAccessor, logger) { } @@ -131,7 +130,7 @@ class CustomOidcConfigureOptions : ConfigureAuthenticationOptions(); } @@ -140,21 +139,21 @@ public void Configure(IServiceCollection services) ### Callback Paths As part of the architecture of the dynamic providers feature, the various callback paths are required and are automatically set to follow a convention. -The convention of these paths follows the form of *~/federation/{scheme}/{suffix}*. +The convention of these paths follows the form of _~/federation/{scheme}/{suffix}_. -These are three paths that are set on the *OpenIdConnectOptions*: +These are three paths that are set on the _OpenIdConnectOptions_: -* [CallbackPath](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.remoteauthenticationoptions.callbackpath). This is the OIDC redirect URI protocol value. The suffix "/signin" is used for this path. -* [SignedOutCallbackPath](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions.signedoutcallbackpath). This is the OIDC post logout redirect URI protocol value. The suffix "/signout-callback" is used for this path. -* [RemoteSignOutPath](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions.remotesignoutpath). This is the OIDC front channel logout URI protocol value. The suffix "/signout" is used for this path. +- [CallbackPath](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.remoteauthenticationoptions.callbackpath). This is the OIDC redirect URI protocol value. The suffix "/signin" is used for this path. +- [SignedOutCallbackPath](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions.signedoutcallbackpath). This is the OIDC post logout redirect URI protocol value. The suffix "/signout-callback" is used for this path. +- [RemoteSignOutPath](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions.remotesignoutpath). This is the OIDC front channel logout URI protocol value. The suffix "/signout" is used for this path. This means for your IdentityServer running at "https://sample.duendesoftware.com" and an OIDC identity provider whose scheme is "idp1", your client configuration with the external OIDC identity provider would be: -* The redirect URI would be "https://sample.duendesoftware.com/federation/idp1/signin" -* The post logout redirect URI would be "https://sample.duendesoftware.com/federation/idp1/signout-callback" -* The front channel logout URI would be "https://sample.duendesoftware.com/federation/idp1/signout" +- The redirect URI would be "https://sample.duendesoftware.com/federation/idp1/signin" +- The post logout redirect URI would be "https://sample.duendesoftware.com/federation/idp1/signout-callback" +- The front channel logout URI would be "https://sample.duendesoftware.com/federation/idp1/signout" ### DynamicProviderOptions -The *DynamicProviderOptions* is a new options class in the IdentityServer options object model. +The _DynamicProviderOptions_ is a new options class in the IdentityServer options object model. It provides [shared settings]({{< ref "/reference/options#dynamic-providers">}}) for the dynamic identity providers feature. 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); } } ``` diff --git a/IdentityServer/v7/docs/content/ui/login/dynamicproviders.md b/IdentityServer/v7/docs/content/ui/login/dynamicproviders.md index 5f6977f0..0d26a9d0 100644 --- a/IdentityServer/v7/docs/content/ui/login/dynamicproviders.md +++ b/IdentityServer/v7/docs/content/ui/login/dynamicproviders.md @@ -114,7 +114,8 @@ For example: ```cs class CustomOidcConfigureOptions : ConfigureAuthenticationOptions { - public CustomOidcConfigureOptions(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor) + public CustomOidcConfigureOptions(IHttpContextAccessor httpContextAccessor, + ILogger logger) : base(httpContextAccessor, logger) { } @@ -131,7 +132,7 @@ class CustomOidcConfigureOptions : ConfigureAuthenticationOptions(); }