Skip to content
We just launched Duende IdentityServer v7.2.0 and BFF v3.0. Check it out!

Dynamic Providers

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 of Duende IdentityServer, enables providers to be configured dynamically from a store at runtime.

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.

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.

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.

Support for Dynamic Identity Providers is included in the Duende IdentityServer Enterprise Edition.

Dynamic identity providers are configured in IdentityServer and require a store for the configuration data of dynamic OIDC providers.

There are two store implementations provided by Duende IdentityServer:

You could also implement your own store based on the IIdentityProviderStore interface.

The configuration data for the OIDC provider is used to assign the configuration on the ASP.NET Core OpenID Connect Options 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.

Here’s an example of adding a dynamic provider to an IdentityServer instance using the in-memory store:

Program.cs
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:

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

Section titled “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) can be used to query the database containing the dynamic providers.

IIdentityProviderStore
/// <summary>
/// Interface to model storage of identity providers.
/// </summary>
public interface IIdentityProviderStore
{
/// <summary>
/// Gets all identity providers name.
/// </summary>
Task<IEnumerable<IdentityProviderName>> GetAllSchemeNamesAsync();
// other APIs omitted
}

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.

In the IdentityServer Quickstart UI, 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:

Login.cshtml.cs
var schemes = await _schemeProvider.GetAllSchemesAsync();
var providers = schemes
.Where(x => x.DisplayName != null)
.Select(x => new ExternalProvider
{
DisplayName = x.DisplayName ?? x.Name,
AuthenticationScheme = x.Name
}).ToList();
var dynamicSchemes = (await _identityProviderStore.GetAllSchemeNamesAsync())
.Where(x => x.Enabled)
.Select(x => new ExternalProvider
{
AuthenticationScheme = x.Scheme,
DisplayName = x.DisplayName
});
providers.AddRange(dynamicSchemes);

The above code will query the identity provider store for all statically registered authentication schemes and merge them with (enabled) dynamic providers.

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}.

There are three paths that are set on the OpenIdConnectOptions:

  • CallbackPath. This is the OIDC redirect URI protocol value. The suffix "/signin" is used for this path.
  • SignedOutCallbackPath. This is the OIDC post logout redirect URI protocol value. The suffix "/signout-callback" is used for this path.
  • 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

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.

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<OpenIdConnectOptions> implementation. In the Configure(string, OpenIdConnectOptions) method, you can override the OpenIdConnectOptions for the provider by name.

CustomConfig.cs
public class CustomConfig : IConfigureNamedOptions<OpenIdConnectOptions>
{
public void Configure(string name, OpenIdConnectOptions options)
{
if (name == "MyScheme")
{
// ... configure options
}
else if (name == "OtherScheme")
{
// ... configure options
}
}
public void Configure(OpenIdConnectOptions options)
{
}
}

You will need to register the named options type in the ASP.NET Core service container at startup:

Program.cs
builder.Services.ConfigureOptions<CustomConfig>();

Accessing OidcProvider Data In IConfigureNamedOptions

Section titled “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<OpenIdConnectOptions>.

This abstraction requires your code to derive from ConfigureAuthenticationOptions<OpenIdConnectOptions, OidcProvider> (rather than IConfigureNamedOptions<OpenIdConnectOptions>).

Here’s an example implementation:

CustomOidcConfigureOptions.cs
class CustomOidcConfigureOptions : ConfigureAuthenticationOptions<OpenIdConnectOptions, OidcProvider>
{
public CustomOidcConfigureOptions(IHttpContextAccessor httpContextAccessor,
ILogger<CustomOidcConfigureOptions> logger) : base(httpContextAccessor, logger)
{
}
protected override void Configure(ConfigureAuthenticationContext<OpenIdConnectOptions, OidcProvider> context)
{
var oidcProvider = context.IdentityProvider;
var oidcOptions = context.AuthenticationOptions;
// TODO: configure oidcOptions with values from oidcProvider
}
}

You will need to register the options type in the service provider at startup:

Program.cs
builder.Services.ConfigureOptions<CustomOidcConfigureOptions>();

The DynamicProviderOptions is an options class in the IdentityServer options object model, and provides shared configuration options for the dynamic identity providers feature. For example, you can customize the path prefix for the dynamic providers callback path:

Program.cs
builder.Services
.AddIdentityServer(options =>
{
// ...
options.DynamicProviders.PathPrefix = "/fed";
// ...
})

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.

We have two samples that show how to use non-OIDC authentication handlers with dynamic identity providers:

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<T, TOptions, TIdentityProvider>(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 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

Section titled “1. Implement A Custom IdentityProvider Type”

While IdentityServer’s IdentityProvider 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 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:

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

Section titled “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.

Program.cs
builder.Services
.AddIdentityServer(options =>
{
// ...
options.DynamicProviders
.AddProviderType<GoogleHandler, GoogleOptions, GoogleIdentityProvider>(
GoogleIdentityProvider.ProviderType);
})

3. Configure Authentication Handler Options

Section titled “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.

To do so, you can use the ConfigureAuthenticationOptions<TOptions, TIdentityProvider> base class. In our Google example:

GoogleDynamicConfigureOptions.cs
class GoogleDynamicConfigureOptions
: ConfigureAuthenticationOptions<GoogleOptions, GoogleIdentityProvider>
{
public GoogleDynamicConfigureOptions(IHttpContextAccessor httpContextAccessor,
ILogger<GoogleDynamicConfigureOptions> logger) : base(httpContextAccessor, logger)
{
}
protected override void Configure(
ConfigureAuthenticationContext<GoogleOptions, GoogleIdentityProvider> 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:

Program.cs
builder.Services.ConfigureOptions<GoogleDynamicConfigureOptions>();

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.

Program.cs
builder.Services.ConfigureOptions<OAuthPostConfigureOptions<GoogleOptions, GoogleHandler>>();

With these building blocks in place, you can start using a custom identity provider type with Duende IdentityServer dynamic identity providers!

Program.cs
builder.Services
.AddIdentityServer(options =>
{
// ...
options.DynamicProviders
.AddProviderType<GoogleHandler, GoogleOptions, GoogleIdentityProvider>(
GoogleIdentityProvider.ProviderType);
})
.AddInMemoryIdentityProviders(new []
{
new GoogleIdentityProvider
{
Scheme = "google1",
DisplayName = "Google 1",
Enabled = true,
ClientId = "...",
ClientSecret = "..."
}
})