Our SAML component supports the Dynamic Providers feature of Duende IdentityServer.
NuGet Packages
The support for Dynamic Providers is included in our Duende IdentityServer specific SAML libraries by default since version 5.1.0.
dotnet add package Rsk.Saml.DuendeIdentityServer
You will also need to install our EntityFramework (EF) library if you are using the EF store.
dotnet add package Rsk.Saml.DuendeIdentityServer.EntityFramework
Registration
To register the SAML Dynamic Provider feature, you will need to use the AddSamlDynamicProvider
extension method. This enables IdentityServer to understand SAML authentication handlers.
services.AddIdentityServer()
// Other configuration code removed for brevity
.AddSamlDynamicProvider();
Identity Provider Stores
Duende IdentityServer only provides OIDC-specific IIdentityProviderStore
stores. We extend these stores to handle SAML dynamic providers. If you wish to extend the implementation with further support for other authentication handlers, we recommend extending our store implementations. This will ensure that your base implementation supports both OIDC and SAML.
In-memory Identity Provider Store
Duende IdentityServer offers an in-memory identity provider store since v6.0.0, using the extension method AddInMemoryOidcProviders
. However, this store can only handle OIDC identity providers.
We provide an in-memory implementation of the IIdentityProviderStore
interface which can handle any type of identity provider. This means that it can handle any new identity provider types without the need for extension or modification.
To register the in-memory store, you will need to use the AddInMemoryIdentityProviders
extension method.
services.AddIdentityServer()
// Other configuration code removed for brevity
.AddSamlDynamicProvider()
.AddInMemoryIdentityProviders(new List<IdentityProvider>
{
new SamlDynamicIdentityProvider
{
Scheme = "samlScheme",
SamlAuthenticationOptions = new Saml2pAuthenticationOptions()
},
new OidcProvider
{
Scheme = "oidcScheme"
}
});
EntityFramework SAML Identity Provider Store
To use our implementation of the EntityFramework SAML Identity Provider Store, you will need to use the IdentityServer extension method, AddIdentityProviderStore
. The DI container will use the last registered implementation of the IIdentityProviderStore
. Therefore, our store must be registered after IdentityServer.
services.AddIdentityServer()
// Other configuration code removed for brevity
.AddSamlDynamicProvider()
.AddIdentityProviderStore<SamlIdentityProviderStore>();
Configuring Unstorable/Reusable Properties
There may be some SAML configuration that you wish not to store in the database because it is unstorable or reusable across multiple SAML identity providers. This includes the SAML license applied to all SAML identity providers or events configured for a specific authentication scheme.
The AddSamlDynamicProvider
extension method takes the optional parameter Action<Saml2pAuthenticationOptions>
. This overwrites any stored configuration values.
services.AddIdentityServer()
// Other configuration code removed for brevity
.AddSamlDynamicProvider(optionsAugmentor =>
{
// license applied to all SAML identity providers
optionsAugmentor.LicenseKey = "<LicenseKey>";
optionsAugmentor.Licensee = "<Licensee>";
// events specific to an identity provider
if (options.IdentityProviderOptions.EntityId == "myIdp")
{
options.Events = new RemoteAuthenticationEvents();
}
})
Callback Paths
Duende IdentityServer's Dynamic Providers feature requires the callback paths to use the convention ~/federation/{scheme}/{suffix}
.
The path prefix defaults to federation
, but can be modified using the IdentityServer DynamicProviderOptions.PathPrefix
option.
Therefore, all the SAML callback paths must follow this convention set by IdentityServer when using this feature.
Check out IdentityServer documentation for more details.
.AddInMemoryIdentityProviders(new List<SamlDynamicIdentityProvider>
{
new SamlDynamicIdentityProvider
{
// Other configuration code removed for brevity
SamlAuthenticationOptions = new Saml2pAuthenticationOptions
{
CallbackPath = "/federation/saml-sp/signin",
ServiceProviderOptions = new SpOptions
{
MetadataPath = "/federation/saml-sp/metadata"
}
// Required if using logout
options.SignedOutCallbackPath = "/federation/saml-sp/signout";
// Required if using HTTP-Artifact binding to send messages
options.ArtifactResolutionService = "/federation/saml-sp/artifactResolution";
},
Scheme = "saml-sp"
}
});
Migrating from Rsk Dynamic Authentication Providers Component
This section covers the migration process from our Rsk Dynamic Authentication Providers component to Duende Dynamic Providers.
Migrating Augmented Options
Rsk Augmented Options:
services.AddDynamicProviders()
.AddSaml(optionsAugmentor =>
{
optionsAugmentor.LicenseKey = "<LicenseKey>";
optionsAugmentor.Licensee = "<Licensee>";
}
Duende Augmented Options:
services.AddIdentityServer()
// Other configuration code removed for brevity
.AddSamlDynamicProvider(optionsAugmentor =>
{
optionsAugmentor.LicenseKey = "<LicenseKey>";
optionsAugmentor.Licensee = "<Licensee>";
}
Migrating Database Values
We serialize the SAML configuration options in the same way as our Rsk Dynamic Authentication Providers component. This means that you can bring across your serialized SAML options into Duende Dynamic Providers without too many modifications.
The following changes must be applied when migrating from Rsk Dynamic Authentication Providers to Duende Dynamic Providers:
Name
becomesScheme
HandlerType
is removed.Type
is introduced, which is alwayssaml2p
- The value of
Options
is added to theProperties
The Rsk Options
property value is a JSON string that needs to be added to the Duende Properties
dictionary with the key SamlAuthenticationOptions
. As the Rsk Options
value is a JSON string, the quotation marks must be escaped before the string is placed inside the Duende Properties
dictionary. Otherwise, the JSON parser will fail with a JsonException.
You can escape the Rsk Options
value manually by replacing "
with \"
, or you can use Newtonsoft Json serializer in C#, as follows:
var duendeProperties = JsonConvert.SerializeObject(new Dictionary<string, string> {{ "SamlAuthenticationOptions", RskOptionsValue }});
Note: The following examples use JSON to represent key/value pairs stored in a database
Rsk Database Value:
{
"Name": "saml",
"DisplayName": "SAML 2.0",
"HandlerType": "Rsk.AspNetCore.Authentication.Saml2p.Saml2pAuthenticationHandler, Rsk.Saml, Version=4.0.0, Culture=neutral, PublicKeyToken=null",
"Options": "{"ServiceProviderOptions":{"EntityId":"https://localhost:5001/saml"},"IdentityProviderOptions":{"EntityId":"https://localhost:5000","SingleSignOnEndpoint":{"Endpoint":"https://localhost:5000/saml/sso","BindingType":1}},"CallbackPath":"/saml/acs","SignInScheme":"local-cookie"}"
}
Duende Database Value:
{
"Scheme": "saml",
"DisplayName": "SAML 2.0",
"Type": "saml2p",
"Enabled": "true",
"Properties": "{"SamlAuthenticationOptions":"{\"ServiceProviderOptions\":{\"EntityId\":\"https://localhost:5001/saml\"},\"IdentityProviderOptions\":{\"EntityId\":\"https://localhost:5000\",\"SingleSignOnEndpoint\":{\"Endpoint\":\"https://localhost:5000/saml/sso\",\"BindingType\":1}},\"CallbackPath\":\"/saml/acs\",\"SignInScheme\":\"local-cookie\"}"}"
}
Custom Serialization of SAML Configuration
Serialization of the SAML configuration options takes place within the SamlDynamicIdentityProvider
object. This is due to the way Duende IdentityServer handles the serialization of the dynamic identity providers.
The SamlDynamicIdentityProvider
class contains a static property called SerializerSettings
. Using this property, you can change how the SAML configuration options get serialized into JSON.
SamlDynamicIdentityProvider.SerializerSettings.Converters.Add(new CustomSerializer());