This is a post that I have wanted to write for a while.

A use case for this could be two companies who want to federate with each other and who both use idsrv4. Now in the world of modern authentication, you would do this with OpenID Connect but since I’m writing a SAML series, we’ll stick with that.

It’s built around the ComponentSpace (CS) SAML for .NET Core stack.

There isn’t a lot of documentation around CS as both a SP and an IDP or around connecting two instances of idsrv4 together so hopefully this post fills some of those gaps.

Thanks to the CS team for their help getting this working.

Essentially what we have is:

CS test client using SAML → idsrv4 running as a IDP and a SP → idsrv4 running as a IDP

The idsrv4 running as a IDP and a SP runs on port 6000 so lets call that idsrv4:6000. The idsrv4 running just as a IDP runs on port 7000 so lets call that idsrv4:7000.

So expanding the above we get:

CS test client using SAML → idsrv4:6000 running as a CS IDP → idsrv4:6000 running as an CS SP → idsrv4:7000 running as a CS IDP

The first part of this is covered here.

As always, all the code and configuration is in a gist.

To get idsrv4 running on either 6000 or 7000, just copy / replace localhost:5000 to the one you want.

Also in the launchSettings.json, the two instances of idsrv4 need two seperate SSL ports. I used:

“sslPort”: 44338

“sslPort”: 44340

Note that IIS Express only allows a limited range of ports for SSL i.e.

44300 to 44399 (inclusive)

You need to copy over all the certificates from the ExampleSP to a Certificate directory under Host:

Now we want a button on the HRD screen for idsrv4:6000 that we can click to get to idsrv4:7000.

The code in AddExternalIdentityProviders is:

.AddSaml("idsrv4", "IdentityServer4", options =>                       {options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;                           options.SignOutScheme = IdentityServerConstants.SignoutScheme;                           options.AssertionConsumerServicePath = "http://localhost:7000/SAML/AssertionConsumerService";                           options.PartnerName = () => "https://IdentityServer4-2";                       })

The appsettings.json for idsrv4:6000 contains a SAML section:

"SAML": {
"$schema": "https://www.componentspace.com/schemas/saml-config-schema-v1.0.json",
"Configurations": [
{
"LocalIdentityProviderConfiguration": {
"Description": "IdentityServer4",
"LocalCertificates": [
{
"FileName": "certificates/idp.pfx",
"Password": "password"
}
],
"Name": "https://IdentityServer4",
"SingleLogoutServiceUrl": "http://localhost:6000/SAML/SingleLogoutService",
"SingleSignOnServiceUrl": "http://localhost:6000/SAML/SingleSignOnService"
},
"LocalServiceProviderConfiguration": {
"AssertionConsumerServiceUrl": "http://localhost:6000/SAML/AssertionConsumerService",
"Description": "IdentityServer4",
"LocalCertificates": [
{
"FileName": "certificates/sp.pfx",
"Password": "password"
}
],
"Name": "https://IdentityServer4",
"SingleLogoutServiceUrl": "http://localhost:6000/SAML/SingleLogoutService"
},
"PartnerIdentityProviderConfigurations": [
{
"Description": "IdentityServer4-2",
"Name": "https://IdentityServer4-2",
"PartnerCertificates": [
{
"FileName": "certificates/idp.cer"
}
],
"SignAuthnRequest": true,
"SingleLogoutServiceUrl": "http://localhost:7000/SAML/SingleLogoutService",
"SingleSignOnServiceUrl": "http://localhost:7000/SAML/SingleSignOnService"
}
],
"PartnerServiceProviderConfigurations": [
{
"AssertionConsumerServiceUrl": "https://localhost:44360/SAML/AssertionConsumerService",
"Description": "Example Service Provider",
"Name": "https://ExampleServiceProvider",
"PartnerCertificates": [
{
"FileName": "certificates/sp.cer"
}
],
"SignSamlResponse": true,
"SingleLogoutServiceUrl": "https://localhost:44360/SAML/SingleLogoutService",
"WantAuthnRequestSigned": true
}
]
}
]
},

Note that localhost:44360 is the CS SAML test client.

The appsettings.json for idsrv4:7000 contains a SAML section:

"SAML": {
"$schema": "https://www.componentspace.com/schemas/saml-config-schema-v1.0.json",
"Configurations": [
{
"LocalIdentityProviderConfiguration": {
"Description": "IdentityServer4-2",
"LocalCertificates": [
{
"FileName": "certificates/idp.pfx",
"Password": "password"
}
],
"Name": "https://IdentityServer4-2",
"SingleLogoutServiceUrl": "http://localhost:7000/SAML/SingleLogoutService",
"SingleSignOnServiceUrl": "http://localhost:7000/SAML/SingleSignOnService"
},
"PartnerServiceProviderConfigurations": [
{
"AssertionConsumerServiceUrl": "http://localhost:6000/SAML/AssertionConsumerService",
"Description": "IdentityServer4",
"Name": "https://IdentityServer4",
"PartnerCertificates": [
{
"FileName": "certificates/sp.cer"
}
],
"SignSamlResponse": true,
"SingleLogoutServiceUrl": "http://localhost:6000/SAML/SingleLogoutService",
"WantAuthnRequestSigned": true
}
]
}
]
},

You can see the code for the two Startup.cs instances in the gist.

So let’s start it up.

In the SAML test client:

Click the button.

This takes us the to the HRD page of idsrv4:6000.

Note the IdentityServer4 button that we added. Click it.

And this takes us to the HRD page of localhost:7000. Note the difference in the HRD screens.

Now just to make sure that we are actually authenticating on the correct instance of idsrv4, I added another user to TestUsers.cs on idsrv4:7000.

And since we already have bob and alice, who else would I use but eve!

new TestUser{SubjectId = "88421118", Username = "eve", Password = "eve",
Claims =
{
new Claim(JwtClaimTypes.Name, "Eve Smith"),
new Claim(JwtClaimTypes.GivenName, "Eve"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "EveSmith@email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.WebSite, "http://eve.com"),
new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json),
new Claim("location", "somewhere")
}
}

Let’s authenticate as eve.

Success!

All good!

--

--

Rory Braybrook
The new control plane

NZ Microsoft Identity dude and MVP. Azure AD/B2C/ADFS/Auth0/identityserver. StackOverflow: https://bit.ly/2XU4yvJ Presentations: http://bit.ly/334ZPt5