Skip to content
Introducing the next era of Duende IdentityServer. Read our CEO’s announcement

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 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(), AddSamlServiceProvider(), 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.

IdentityServer includes built-in support for OIDC providers and SAML providers. You can also add custom authentication handlers for other protocols.

Dynamic identity providers require a store for the configuration data. There are two store implementations provided by Duende IdentityServer:

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

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.

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 OIDC provider using the in-memory store:

Program.cs
builder.Services
.AddIdentityServer(options =>
{
// ...
})
.AddInMemoryOidcProviders(new []
{
new OidcProvider
{
Scheme = "oidc",
DisplayName = "Sample provider",
Enabled = true,
// ... more properties
}
})

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");
}
}

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 for OIDC dynamic providers:

  • 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

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>();

IdentityServer includes built-in support for dynamic SAML 2.0 providers via AddSamlDynamicProvider(). This registers the SAML SP authentication handler for use with the dynamic provider infrastructure, so you can manage SAML IdPs from a store at runtime.

To enable SAML dynamic providers, call AddSamlDynamicProvider() on the IdentityServer builder:

Program.cs
builder.Services.AddIdentityServer()
.AddSamlDynamicProvider();

SAML dynamic providers use the SamlProvider model, which extends IdentityProvider with SAML-specific properties:

  • IdpEntityId (string?, default null) — The entity ID of the remote SAML Identity Provider.
  • SingleSignOnServiceUrl (string?, default null) — The URL of the IdP’s SSO endpoint.
  • SingleLogoutServiceUrl (string?, default null) — The URL of the IdP’s SLO endpoint. When null, outbound logout is disabled.
  • SigningCertificateBase64 (string?, default null) — Base64-encoded X.509 certificate for validating IdP signatures.
  • BindingType (string, default "redirect") — The SAML binding type ("redirect" or "post").
  • SpEntityId (string?, default null) — The entity ID of your application (the SP). When null, derived from the IdentityServer issuer.
  • AllowUnsolicitedAuthnResponse (bool, default false) — Whether to accept IdP-initiated (unsolicited) responses.
  • WantAssertionsSigned (bool, default true) — Whether assertions from the IdP must be signed.
  • OutboundSigningAlgorithm (string, default RSA-SHA256) — The XML signature algorithm for outbound requests.
  • SpSigningCertificateBase64 (string?, default null) — Base64-encoded PKCS#12 certificate (with private key) that your SP uses to sign outbound SAML messages such as AuthnRequest and LogoutResponse. Set this when the remote IdP requires signed requests or when you use single logout.
  • SpSigningCertificatePassword (string?, default null) — Optional password for the PKCS#12 certificate supplied in SpSigningCertificateBase64. Omit this property if the certificate has no password.

SamlProvider also inherits Scheme, DisplayName, Enabled, and the Properties dictionary from IdentityProvider.

For development and testing, use the in-memory store:

Program.cs
builder.Services.AddIdentityServer()
.AddSamlDynamicProvider()
.AddInMemorySamlProviders(new[]
{
new SamlProvider
{
Scheme = "corporate-idp",
DisplayName = "Corporate ADFS",
Enabled = true,
IdpEntityId = "https://adfs.corporate.example.com",
SingleSignOnServiceUrl = "https://adfs.corporate.example.com/adfs/ls/",
SigningCertificateBase64 = "<base64-encoded certificate>",
}
});

For production, use the Entity Framework Core store. SamlProvider records are stored in the IdentityProviers table and managed via the ConfigurationDbContext.

For SAML dynamic providers, the module path is set to ~/federation/{scheme}/Saml2. The SAML handler registers its own ACS and SLO callback endpoints under that path automatically.

For your IdentityServer running at https://sample.duendesoftware.com and a SAML provider whose scheme is “corporate-idp”, the ACS endpoint would be at https://sample.duendesoftware.com/federation/corporate-idp/Saml2/Acs.

For static SAML provider registration (when you have a small, fixed set of SAML IdPs), see SAML 2.0 External Provider instead.

Accessing SamlProvider Data in IConfigureNamedOptions

Section titled “Accessing SamlProvider Data in IConfigureNamedOptions”

If your customization of SAML authentication options requires per-provider data available in the SamlProvider, Duende IdentityServer provides an abstraction for IConfigureNamedOptions<SamlAuthenticationOptions>.

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

The SamlAuthenticationOptions instance is pre-populated from the SamlProvider configuration. Your overrides take priority over the stored provider values, which in turn take priority over defaults.

Here’s an example implementation:

CustomSamlConfigureOptions.cs
class CustomSamlConfigureOptions
: ConfigureAuthenticationOptions<SamlAuthenticationOptions, SamlProvider>
{
public CustomSamlConfigureOptions(IHttpContextAccessor httpContextAccessor,
ILogger<CustomSamlConfigureOptions> logger) : base(httpContextAccessor, logger)
{
}
protected override void Configure(
ConfigureAuthenticationContext<SamlAuthenticationOptions, SamlProvider> context)
{
var samlProvider = context.IdentityProvider;
var samlOptions = context.AuthenticationOptions;
// TODO: configure samlOptions with values from samlProvider
}
}

Register the options type in the service provider at startup:

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

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 the display names and scheme names of all registered identity providers.
/// </summary>
Task<IReadOnlyCollection<IdentityProviderName>> GetAllSchemeNamesAsync(CancellationToken ct);
// other APIs omitted
}

The GetAllSchemeNamesAsync API returns a read-only collection 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(HttpContext.RequestAborted))
.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.

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 built-in implementations for OpenID Connect and SAML 2.0 providers. In your solution, it may be necessary to support other authentication providers as well.

We have two samples that show how to use other 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.

The recommended way to register other authentication handlers is the AddDynamicProvider<THandler, TOptions, TIdentityProvider, TConfigureOptions> extension method on the IdentityServer builder. It takes care of provider type mapping, configure options registration, and handler service registration in a single call.

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 Microsoft.AspNetCore.Authentication.Google 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 GoogleIdentityProvider(IdentityProvider other)
: base(ProviderType, other)
{
}
public string? ClientId
{
get => this["ClientId"];
set => this["ClientId"] = value;
}
public string? ClientSecret
{
get => this["ClientSecret"];
set => this["ClientSecret"] = value;
}
}

2. Configure Authentication Handler Options

Section titled “2. Configure Authentication Handler Options”

You need to implement a class that maps from your identity provider model to the authentication handler options. Derive from ConfigureAuthenticationOptions<TOptions, TIdentityProvider>:

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";
}
}

Use the AddDynamicProvider extension method on the IdentityServer builder to register everything in one call. This registers the provider type mapping, the configure options, and the authentication handler:

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

AddDynamicProvider handles:

  • Registering the provider type mapping (AddProviderType)
  • Registering the configure options as IConfigureOptions<TOptions>
  • Registering the authentication handler in DI (via TryAddTransient)