diff --git a/src/content/docs/identitymodel/endpoints/dynamic-registration.md b/src/content/docs/identitymodel/endpoints/dynamic-registration.md index 2d92858d7..21b352182 100644 --- a/src/content/docs/identitymodel/endpoints/dynamic-registration.md +++ b/src/content/docs/identitymodel/endpoints/dynamic-registration.md @@ -8,9 +8,9 @@ redirect_from: - /foss/identitymodel/endpoints/dynamic_registration/ --- -The client library for [OpenID Connect Dynamic Client -Registration](https://openid.net/specs/openid-connect-registration-1_0.html) -is provided as an extension method for *HttpClient*. +The client library for [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html) +is provided as an extension method for [ +`System.Net.Http.HttpClient`](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient). The following code sends a registration request: diff --git a/src/content/docs/identityserver/ui/login/dynamicproviders.md b/src/content/docs/identityserver/ui/login/dynamicproviders.md index f8d7eb4c9..0a008127c 100644 --- a/src/content/docs/identityserver/ui/login/dynamicproviders.md +++ b/src/content/docs/identityserver/ui/login/dynamicproviders.md @@ -9,30 +9,113 @@ redirect_from: - /identityserver/v7/ui/login/dynamicproviders/ --- -Dynamic Identity Providers are a scalable solution for managing authentication with numerous external providers without -incurring performance penalties or requiring application recompilation. This feature, included in the Enterprise Edition +Dynamic Identity Providers are a scalable solution for managing authentication with lots of external providers, without +incurring performance penalties or requiring application recompilation. This feature, included in the [Enterprise Edition](/general/licensing/#enterprise-edition) of Duende IdentityServer, enables providers to be configured dynamically from a store at runtime. ## 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 ASP.NET Core service provider. 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. +Authentication handlers for external providers are typically added into your IdentityServer using `AddAuthentication()`, +`AddOpenIdConnect()`, `AddSaml2()`, and other helper methods. This is fine for a handful of schemes, but becomes harder +to manage if you have too many of them. +Additionally, you'd have to re-run your startup code for new authentication handlers to be picked up by ASP.NET Core. -Duende IdentityServer provides support for dynamic configuration of OpenID Connect providers loaded from a store. This -is designed to address the performance concern and allowing changes to the configuration to a running server. +The authentication handler architecture in ASP.NET Core was not designed to have many statically registered authentication +handlers registered in the service container and Dependency Injection (DI) system. At some point you will incur a +performance penalty for having too many of them. -Support for Dynamic Identity Providers is included -in [IdentityServer](https://duendesoftware.com/products/identityserver) Enterprise Edition. +Duende IdentityServer provides support for dynamic configuration of authentication handlers loaded from a store. +Dynamic configuration addresses the performance concern and allows changes to the configuration to a running server. -### Listing Dynamic Providers On The Login Page +Support for Dynamic Identity Providers is included in the [Duende IdentityServer](https://duendesoftware.com/products/identityserver) Enterprise Edition. -The [identity provider store](/identityserver/reference/stores/idp-store/) can be used to query the database +## Store And Configuration Data + +Dynamic identity providers are configured in IdentityServer and require a store for the configuration data of [dynamic OIDC providers](../../../reference/models/idp/). + +There are two store implementations provided by Duende IdentityServer: + +* An in-memory store +* A store backed by a database (using [Entity Framework Core](../../../data/ef/)) + +You could also implement your own store based on the [`IIdentityProviderStore` interface](../../../reference/stores/idp-store/). + +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](../../../reference/models/idp) provides details for the model +properties and how they are mapped to the options. + +:::tip[Consider caching dynamic identity providers] +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](../../../data/configuration#caching-configuration-data). +If you use the EF stores, there is a general helper [to enable caching for all configuration data](../../../data/ef#enabling-caching-for-configuration-store). +::: + +Here's an example of adding a dynamic provider to an IdentityServer instance using the in-memory store: + +```csharp title=Program.cs {6-15} +builder.Services + .AddIdentityServer(options => + { + // ... + }) + .AddInMemoryIdentityProviders(new [] + { + new OidcProvider + { + Scheme = "oidc", + DisplayName = "Sample provider", + Enabled = true, + // ... more properties + } + }) +``` + +The identity provider store only provides an interface to query dynamic providers and does not provide any methods to add, update, or delete identity providers. +For custom store implementations, this means you'll need to implement a mechanism for populating the store with identity providers. + +If you're using the Entity Framework Core identity provider store from the `Duende.IdentityServer.EntityFramework.Storage` NuGet package, +you can use the `ConfigurationDbContext` database context directly to add, update or remove dynamic identity providers: + +```csharp title="SeedData.cs" +private static async Task SeedDynamicProviders(ConfigurationDbContext context) +{ + if (!context.IdentityProviders.Any()) + { + Console.WriteLine("IdentityProviders being populated..."); + + context.IdentityProviders.Add(new OidcProvider + { + Scheme = "demoidsrv", + DisplayName = "IdentityServer (dynamic)", + Authority = "https://demo.duendesoftware.com", + ClientId = "login", + // ... more properties + }.ToEntity()); + + await context.SaveChangesAsync(); + + Console.WriteLine("IdentityProviders populated."); + } + else + { + Console.WriteLine("OidcIdentityProviders already populated"); + } +} +``` + +You can use the `ConfigurationDbContext` database context to add dynamic identity providers at runtime. + +## Listing Dynamic Providers On The Login Page + +When working with dynamic providers, you'll typically want to display a list of the available providers on the login +page. The [identity provider store (`IIdentityProviderStore`)](../../../reference/stores/idp-store/) can be used to query the database containing the dynamic providers. -```cs +```cs title="IIdentityProviderStore" {9} /// /// Interface to model storage of identity providers. /// @@ -47,19 +130,14 @@ public interface IIdentityProviderStore } ``` -These results can then be used to populate the list of options presented to the user on the login page. +The `GetAllSchemeNamesAsync()` API returns a list of `IdentityProviderName` objects, which contain the scheme name and +display name of the provider and can be used on the login page, or in other places where you need this information. -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/products/tree/main/identity-server/templates/src/UI/Pages/Account/Login/Index.cshtml.cs#l193-l210) -uses both interfaces to then present a merged and unified list to the end user: +In the [IdentityServer Quickstart UI](https://github.com/DuendeSoftware/products/tree/main/identity-server/templates/src/UI/Pages/Account/Login/Index.cshtml.cs#l193-l210), +dynamically registered identity providers will be automatically added to the list of providers on the login page by querying the identity provider store. +In custom UI implementations, you can use a similar approach to build and present a unified list of authentication providers to the end user: -```cs +```cs title="Login.cshtml.cs" var schemes = await _schemeProvider.GetAllSchemesAsync(); var providers = schemes @@ -81,40 +159,69 @@ var dynamicSchemes = (await _identityProviderStore.GetAllSchemeNamesAsync()) providers.AddRange(dynamicSchemes); ``` -### Store And Configuration Data - -To use the dynamic providers feature an [identity provider store](/identityserver/reference/stores/idp-store/) must be -provided that will load [model data](/identityserver/reference/models/idp/) for the OIDC identity provider to be used. -If you're using the [Entity Framework Integration](/identityserver/data/ef/) then this is implemented for you. +The above code will query the identity provider store for all statically registered authentication schemes and merge them with (enabled) dynamic providers. :::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. -If you use a custom store, there is -an [extension method to enable caching](/identityserver/data/configuration#caching-configuration-data). -If you use the EF stores, there is general -helper [to enable caching for all configuration data](/identityserver/data/ef#enabling-caching-for-configuration-store). +The dynamic identity provider store API is deliberately separate from the `IAuthenticationSchemeProvider` provided by ASP.NET Core, which returns the +list of statically configured providers (from `Startup.cs`). + +This split allows the developer to have more control over the customization on the login page. For example, there might be hundreds or +thousands on dynamic providers, and therefore you would not want them displayed on the login page. At the same time, you might have a +few social providers statically configured that you would want to display. ::: -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](/identityserver/reference/models/idp) provides details for the model -properties and how they are mapped to the options. +## Callback Paths + +As part of the architecture of the dynamic providers feature, different callback paths are required and are +automatically set to follow a convention. The convention of these paths follows the form of `~/federation/{scheme}/{suffix}`. -#### Customizing OpenIdConnectOptions +:::tip +Even if you don't use dynamic providers yet, you may want to consider adopting this pattern for the callback paths. +This will make it easier to transition to dynamic providers in the future. +::: -If it is needed to further customize the `OpenIdConnectOptions`, you can register in the ASP.NET Core service provider an instance of -`IConfigureNamedOptions`. For example: +There are three paths that are set on the `OpenIdConnectOptions`: -```cs +* [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. + +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` + +## Advanced Configuration + +Dynamic identity providers in Duende IdentityServer come with a number of defaults and expose configuration options +that make sense in most scenarios. In this section, we'll cover some of the more advanced configuration options. + +### Customizing OpenIdConnectOptions + +While dynamic providers come with various configuration options, these are not as rich as the options available when +statically configuring authentication handlers using `AddOpenIdConnect()`. + +If you need to further customize the `OpenIdConnectOptions` for a particular provider, you can do so using a custom +`IConfigureNamedOptions` implementation. In the `Configure(string, OpenIdConnectOptions)` method, +you can override the `OpenIdConnectOptions` for the provider by name. + +```cs title="CustomConfig.cs" public class CustomConfig : IConfigureNamedOptions { public void Configure(string name, OpenIdConnectOptions options) { if (name == "MyScheme") { - options.ClaimActions.MapAll(); + // ... configure options + } + else if (name == "OtherScheme") + { + // ... configure options } } @@ -124,21 +231,35 @@ public class CustomConfig : IConfigureNamedOptions } ``` -And to register this in the ASP.NET Core service provider: +You will need to register the named options type in the ASP.NET Core service container at startup: -```cs +```csharp title="Program.cs" builder.Services.ConfigureOptions(); ``` -#### Accessing OidcProvider Data In IConfigureNamedOptions +:::note +While you can use constructor injection in your `IConfigureNamedOptions` to access other services, +we recommend using that technique sparingly for performance reasons. If you do need to access your Entity Framework Core +database context here, make sure to cache any additional data you may need as part of configuring options. + +Alternatively, the `OidcProvider` class has a `Properties` bag that can be used to store additional dynamic identity provider +configuration data. You can use this data to further customize the `OpenIdConnectOptions`, instead of making additional database +calls. When you require access to the `Properties` bag or the `IIdentityProvider` store, you can use the `ConfigureAuthenticationOptions<>` +base class to further customize your dynamic identity provider, as we'll see in the next section. +::: + +### Accessing OidcProvider Data In IConfigureNamedOptions + +If your customization of `OpenIdConnectOptions` requires per-provider data that is available in the `IIdentityProvider` +and is accessible through properties of the `OidcProvider`, Duende IdentityServer provides an abstraction +for `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 +Here's an example implementation: + +```cs title="CustomOidcConfigureOptions.cs" class CustomOidcConfigureOptions : ConfigureAuthenticationOptions { public CustomOidcConfigureOptions(IHttpContextAccessor httpContextAccessor, @@ -156,37 +277,175 @@ class CustomOidcConfigureOptions : ConfigureAuthenticationOptions(); ``` -### Callback Paths +### DynamicProviderOptions -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 `DynamicProviderOptions` is an options class in the IdentityServer options object model, and provides +[shared configuration options](../../../reference/options#dynamic-providers) for the dynamic identity providers +feature. For example, you can customize the path prefix for the dynamic providers callback path: -These are three paths that are set on the `OpenIdConnectOptions`: +```csharp title="Program.cs" +builder.Services + .AddIdentityServer(options => + { + // ... + + options.DynamicProviders.PathPrefix = "/fed"; + + // ... + }) +``` -* [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. +## Using Non-OIDC Authentication Handlers -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: +Dynamic identity providers in Duende IdentityServer come with an implementation that supports OpenId Connect providers to be registered. +In your solution, it may be necessary to support other authentication providers, such as a SAML-based authentication provider. -* 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" +We have two samples that show how to use non-OIDC authentication handlers with dynamic identity providers: +* Adding the [WS-Federation protocol type](../../../identityserver/samples/ui/#adding-other-protocol-types-to-dynamic-providers) +* Adding the [Saml2 protocol type](../../../identityserver/samples/ui/#using-sustainsyssaml2-with-dynamic-providers), using the [Sustainsys.Saml2](https://saml2.sustainsys.com/) open source library -### DynamicProviderOptions +In this section, we'll look at a minimal example of how to add other authentication handlers, such as the `GoogleHandler`, to dynamic identity providers, + +To register other authentication handlers, you can use the `AddProviderType(string scheme)` method on the `DynamicProviderOptions` object, +where `T` is the authentication handler type, `TOptions` is the options type for that particular handler, and `TIdentityProvider` is the identity provider type that models the dynamic provider. + +The authentication handler type and options type will typically be provided by the authentication provider itself. +For example, the `GoogleHandler` and `GoogleOptions` types are provided by the `Google.AspNetCore.Authentication.OAuth` NuGet package. +`TIdentityProvider` will typically be a model class that maps to the identity provider data in the database +and can either be IdentityServer's [`IdentityProvider`](../../../reference/models/idp) class, or a custom type provided and implemented by you. + +Let's add Google authentication support to dynamic identity providers in IdentityServer! + +We'll assume you have already added the `Microsoft.AspNetCore.Authentication.Google` NuGet package to your project. + +### 1. Implement A Custom IdentityProvider Type + +While IdentityServer's [`IdentityProvider`](../../../reference/models/idp) class has a `Properties` bag that can be used +to store dynamic identity provider configuration data, it's recommended to use a custom type that is specific to the dynamic +identity provider. + +The `GoogleIdentityProvider` class can extend IdentityServer's [`IdentityProvider`](../../../reference/models/idp) class, +and expose additional properties that are specific to the Google identity provider. For a minimal Google implementation, +that would be the `ClientId` and `ClientSecret`: + +```csharp title="GoogleIdentityProvider.cs" +public class GoogleIdentityProvider : IdentityProvider +{ + public const string ProviderType = "google"; + + public GoogleIdentityProvider() + : base(ProviderType) + { + } + + public string? ClientId + { + get => this["ClientId"]; + set => this["ClientId"] = value; + } + + public string? ClientSecret + { + get => this["ClientSecret"]; + set => this["ClientSecret"] = value; + } +} +``` + +### 2. Register Dynamic Identity Provider Type + +In the host startup, you can register the handler and identity provider type. This registration provides IdentityServer +with a way to map the dynamic identity provider configuration type created in the previous step, to an authentication handler type +in ASP.NET Core. + +```csharp title="Program.cs" {6} +builder.Services + .AddIdentityServer(options => + { + // ... + + options.DynamicProviders + .AddProviderType( + GoogleIdentityProvider.ProviderType); + }) +``` + +### 3. Configure Authentication Handler Options + +With the dynamic identity provider type mapped to an ASP.NET Core authentication handler type, you'll need to make sure +an instance of the ASP.NET Core authentication handler options can be created based on a particular dynamic provider configuration. -The `DynamicProviderOptions` is a new options class in the IdentityServer options object model. -It provides [shared settings](/identityserver/reference/options#dynamic-providers) for the dynamic identity providers -feature. +To do so, you can use the `ConfigureAuthenticationOptions` base class. In our Google example: + +```csharp title="GoogleDynamicConfigureOptions.cs" +class GoogleDynamicConfigureOptions + : ConfigureAuthenticationOptions +{ + public GoogleDynamicConfigureOptions(IHttpContextAccessor httpContextAccessor, + ILogger logger) : base(httpContextAccessor, logger) + { + } + + protected override void Configure( + ConfigureAuthenticationContext context) + { + var googleProvider = context.IdentityProvider; + var googleOptions = context.AuthenticationOptions; + + googleOptions.ClientId = googleProvider.ClientId; + googleOptions.ClientSecret = googleProvider.ClientSecret; + googleOptions.ClaimActions.MapAll(); + + googleOptions.SignInScheme = context.DynamicProviderOptions.SignInScheme; + googleOptions.CallbackPath = context.PathPrefix + "/signin"; + } +} +``` + +You will need to register this type in the service container at startup: + +```csharp title="Program.cs" +builder.Services.ConfigureOptions(); +``` + +Note that for the `GoogleHandler` to work, you'll also need to register its `OAuthPostConfigureOptions<>` +to make sure data protection and state data formatters are registered. While this is an implementation detail of the +Google authentication handler, a similar implementation detail may exist for the custom dynamic provider type you are building. + +```csharp title="Program.cs" +builder.Services.ConfigureOptions>(); +``` + +### 4. Use A Custom IdentityProvider + +With these building blocks in place, you can start using a custom identity provider type with Duende IdentityServer +dynamic identity providers! + +```csharp title="Program.cs" +builder.Services + .AddIdentityServer(options => + { + // ... + + options.DynamicProviders + .AddProviderType( + GoogleIdentityProvider.ProviderType); + }) + .AddInMemoryIdentityProviders(new [] + { + new GoogleIdentityProvider + { + Scheme = "google1", + DisplayName = "Google 1", + Enabled = true, + ClientId = "...", + ClientSecret = "..." + } + }) +``` \ No newline at end of file