Implementation SAML2 IdP support for IdentityServer4 with .NET core.
This is useful for connecting older relying parties to IdentityServer4.
The underlying SAML2 classes use .NET Core.
The SAML2 endpoints is implemented via an IdentityServer4.Hosting.IEndpointHanlder
.
Endpoint ~/saml2/metadata returns SAML2 metadata, ~/saml2 process SAML2 sing-in and sign-out requests.
This endpoints handles the SAML2P protocol requests and redirects the user to the login page if needed.
The login page will then use the normal return URL mechanism to redirect back to the SAML2 endpoint to create the protocol response.
For most parts, the SAML2 endpoint can use the standard IdentityServer4 client configuration for relying parties. But there are also options available for setting SAML2 specific options.
You can configure global defaults in the Saml2SPOptions
class, e.g.:
- default hashing and digest algorithms
- default SAML name identifier format
- default encryption and keywrap algorithms
- default mappings from "short" claim types to WS-* claim types
- specify Saml2SecurityTokenHandler
The following client settings are used by the SAML2 endpoint:
public static IEnumerable<Client> GetClients()
{
return new[]
{
new Client
{
ClientId = "urn:owinrp", // entityId identifier
ProtocolType = ProtocolTypes.Saml2p, // must be set to SAML2
RedirectUris = { "https://localhost:44334/Saml2/Acs" }, // reply URL
PostLogoutRedirectUris = { "https://localhost:44334/" },
FrontChannelLogoutUri = "https://localhost:44334/Saml2/Acs",
AccessTokenLifetime = 36000, // lifetime of SAML2 token
AllowedScopes = { "openid", "profile" }
},
new Client
{
ClientId = "urn:aspnetcorerp",
ProtocolType = ProtocolTypes.Saml2p,
RedirectUris = { "https://localhost:44302/Auth/AssertionConsumerService" },
PostLogoutRedirectUris = { "https://localhost:44302/Auth/PostLogout" },
FrontChannelLogoutUri = "https://localhost:44302/Auth/Logout",
AccessTokenLifetime = 36000,
AllowedScopes = { "openid", "profile" }
},
new Client
{
ClientId = "urn:aspnetwebapprp",
ProtocolType = ProtocolTypes.Saml2p,
RedirectUris = { "https://localhost:44314/Default.aspx" },
PostLogoutRedirectUris = { "https://localhost:44314/Default.aspx" },
FrontChannelLogoutUri = "https://localhost:44314/Default.aspx",
AccessTokenLifetime = 36000,
AllowedScopes = { "openid", "profile" }
},
};
}
If you want to deviate from the global defaults (e.g. set a different token type or claim mapping) for a specific
relying party, you can define a RelyingParty
object that uses the same realm name as the client ID used above.
This sample contains an in-memory relying party store that you can use to make these relying party specific settings
available to the SAML2 engine (using the AddInMemoryRelyingParty
extension method).
Otherwise, if you want to use your own store, you will need an implementation of IRelyingPartyStore
.
This repo contains an extension method for the IdentityServer builder object to register all the necessary services in DI, e.g.:
services.AddDistributedMemoryCache();
services.AddIdentityServer()
.AddSigningCredential(cert)
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(TestUsers.Users)
.AddSaml2()
.AddAuthorizationParametersMessageStore<DistributedCacheAuthorizationParametersMessageStore>()
.AddInMemoryRelyingParties(Config.GetRelyingParties());
Add to project Abc.IdentityModel.Tokens.Saml via nuget and change SecurityTokenHandlers, e.g.:
builder.AddSaml2(options => {
// Add encrypted SAML2.0 tokens support
options.SecurityTokenHandler = new Abc.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler()
};
});
Use the .NET Core SAML2 middleware to point to the SAML2 endpoint, e.g.:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.Configure<Saml2Configuration>(saml2Configuration => {
saml2Configuration.Issuer = Configuration["saml2:issuer"];
saml2Configuration.AllowedAudienceUris.Add(saml2Configuration.Issuer);
saml2Configuration.SignatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
saml2Configuration.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.ChainTrust;
// !!! REMOVE for production
saml2Configuration.RevocationMode = System.Security.Cryptography.X509Certificates.X509RevocationMode.NoCheck;
// !!! for TEST only
saml2Configuration.SaveBootstrapContext = true;
var entityDescriptor = new EntityDescriptor();
entityDescriptor.ReadIdPSsoDescriptorFromUrl(new Uri(Configuration["saml2:metadata"]));
if (entityDescriptor.IdPSsoDescriptor != null) {
saml2Configuration.SingleSignOnDestination = entityDescriptor.IdPSsoDescriptor.SingleSignOnServices.First().Location;
saml2Configuration.SingleLogoutDestination = entityDescriptor.IdPSsoDescriptor.SingleLogoutServices.First().Location;
saml2Configuration.SignatureValidationCertificates.AddRange(entityDescriptor.IdPSsoDescriptor.SigningCertificates);
}
else {
throw new Exception("IdPSsoDescriptor not loaded from metadata.");
}
// !!! for ADFS
saml2Configuration.SignAuthnRequest = true;
var certPath = Path.Combine(Directory.GetCurrentDirectory(), "saml2.sample.pfx");
if (!File.Exists(certPath)) {
throw new InvalidOperationException($"{certPath} not found");
}
saml2Configuration.SigningCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2(certPath, "abc", System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.MachineKeySet);
});
services.AddSaml2();
}
Use the Katana SAML2 middleware to point to the SAML2 endpoint, e.g.:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions() {
CookiePath = HostingEnvironment.ApplicationVirtualPath,
CookieSameSite = Microsoft.Owin.SameSiteMode.None,
});
app.UseSaml2Authentication(CreateSaml2Options())
}
private static Saml2AuthenticationOptions CreateSaml2Options() {
var spOptions = new SPOptions {
EntityId = new EntityId(realm),
AuthenticateRequestSigningBehavior = SigningBehavior.IfIdpWantAuthnRequestsSigned,
ReturnUrl = new Uri(System.Web.VirtualPathUtility.ToAbsolute("~/Home"), UriKind.Relative),
};
spOptions.ServiceCertificates.Add(new X509Certificate2(
AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "/App_Data/saml2.sample.pfx", "abc"));
var saml2Options = new Saml2AuthenticationOptions(false) {
SPOptions = spOptions
};
var ed = MetadataLoader.LoadIdp(adfsMetadata, false);
var idp = new IdentityProvider(ed.EntityId, spOptions) {
AllowUnsolicitedAuthnResponse = true,
};
idp.ReadMetadata(ed);
saml2Options.IdentityProviders.Add(idp);
return saml2Options;
}
Use the WebForms SAML2 module to point to the SAML2 endpoint, e.g.:
<configuration>
<configSections>
<section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<section name="microsoft.identityModel.saml" type="Microsoft.IdentityModel.Web.Configuration.MicrosoftIdentityModelSamlSection, Microsoft.IdentityModel.Protocols" />
</configSections>
<system.webServer>
<modules>
<remove name="FormsAuthentication" />
<add name="Saml2AuthenticationModule" type="ServiceProvider.MySaml2AuthenticationModule" />
<add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule" />
</modules>
</system.webServer>
<microsoft.identityModel>
<service>
<!-- The set of audience URIs. This should match the entityID specified in metadata\homerealmdiscovery.xml. -->
<audienceUris>
<add value="urn:sample:saml2:fx" />
</audienceUris>
<!-- Enable encrypted tokens -->
<serviceCertificate>
<certificateReference x509FindType="FindBySubjectDistinguishedName" findValue="CN=localhost" storeLocation="LocalMachine" storeName="My"/>
</serviceCertificate>
<!--!!! Remove in production -->
<certificateValidation certificateValidationMode="None" />
<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry">
<!-- The set of trusted issuers. This should match the entityIDs and thumbprints of signing certificates
specified in metadata\identityprovider.xml. -->
<trustedIssuers>
<add thumbprint="6b7acc520305bfdb4f7252daeb2177cc091faae1" name="IdentityServer.Saml2" />
</trustedIssuers>
</issuerNameRegistry>
<!-- The resolver to use when finding this service's signing and encrypting certificates. -->
<serviceTokenResolver type="Samples.Saml.Utilities.SampleServiceProviderSecurityTokenResolver" />
<!-- Enable saveBootstrapTokens so the token visualizer can show the raw SAML assertion. -->
<securityTokenHandlers>
<securityTokenHandlerConfiguration saveBootstrapTokens="true">
</securityTokenHandlerConfiguration>
</securityTokenHandlers>
<federatedAuthentication>
<cookieHandler requireSsl="true" />
</federatedAuthentication>
</service>
</microsoft.identityModel>
</configuration>