~~NOTOC~~
====== Scenario 9.3: SSO Integration ======
**Category:** [[.:start|Authentication]] \\
**Complexity:** **** (High) \\
**Prerequisites:** Identity Provider, Client Certificates \\
**Estimated Time:** 30-45 minutes
----
===== Description =====
This scenario describes the **integration of certificate-based authentication into Single Sign-On (SSO) systems**. Certificates can be used as a primary or additional authentication factor.
**Supported Protocols:**
* **SAML 2.0** - X.509 Certificate Holder Assertion
* **OIDC** - mTLS Client Credentials
* **WS-Federation** - X.509 Token
* **Kerberos PKINIT** - Certificate-to-Kerberos
----
===== Workflow: OIDC with mTLS =====
sequenceDiagram
participant User
participant App
participant IdP as Identity Provider
participant API
User->>App: Access
App->>IdP: Authorization Request
IdP->>User: Request client certificate
User->>IdP: Present certificate
IdP->>IdP: Validate certificate
IdP->>App: ID Token + Access Token
App->>API: Request + Access Token
API->>App: Response
----
===== Code Example: OIDC with Certificate Auth =====
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Certificate;
var builder = WebApplication.CreateBuilder(args);
// Certificate authentication for IdP communication
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie("Cookies")
.AddOpenIdConnect(options =>
{
options.Authority = "https://idp.example.com";
options.ClientId = "my-app";
options.ClientSecret = "secret"; // Or client cert
// mTLS for token endpoint
options.BackchannelHttpHandler = CreateMtlsHandler();
options.ResponseType = "code";
options.SaveTokens = true;
// Certificate claim mapping
options.ClaimActions.MapJsonKey("certificate_thumbprint", "x5t");
options.ClaimActions.MapJsonKey("certificate_subject", "x5t#S256");
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
// Additional validation
var cert = context.HttpContext.Connection.ClientCertificate;
if (cert != null)
{
// Link certificate with token
var identity = context.Principal.Identity as ClaimsIdentity;
identity?.AddClaim(new Claim("client_cert_subject", cert.Subject));
}
return Task.CompletedTask;
}
};
});
static HttpClientHandler CreateMtlsHandler()
{
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(
new X509Certificate2("client.pfx", "Password!")
);
return handler;
}
----
===== Code Example: SAML with X.509 Holder-of-Key =====
using ComponentSpace.Saml2;
public class SamlCertificateAuthService
{
private readonly ISamlServiceProvider _samlServiceProvider;
public async Task AuthenticateWithCertificate(
HttpContext httpContext,
X509Certificate2 clientCert)
{
// Certificate as holder-of-key subject confirmation
var samlResponse = await _samlServiceProvider.ReceiveSSOAsync();
// Check subject confirmation method
if (samlResponse.SubjectConfirmationMethod != "urn:oasis:names:tc:SAML:2.0:cm:holder-of-key")
{
throw new SecurityException("Holder-of-Key confirmation required");
}
// Compare certificate from SAML assertion with presented one
var assertionCert = ExtractCertificateFromAssertion(samlResponse);
if (assertionCert.Thumbprint != clientCert.Thumbprint)
{
throw new SecurityException("Certificate does not match assertion");
}
return samlResponse.ClaimsPrincipal;
}
private X509Certificate2 ExtractCertificateFromAssertion(SamlResponse response)
{
// Extract X.509 certificate from SubjectConfirmationData
var keyInfoXml = response.SubjectConfirmationDataKeyInfo;
// Parse X509Data from KeyInfo
// ...
return null;
}
}
----
===== Active Directory Certificate Mapping =====
public class AdCertificateMappingService
{
private readonly string _ldapPath;
public AdCertificateMappingService(string ldapPath)
{
_ldapPath = ldapPath; // e.g. "LDAP://dc.example.com"
}
public DirectoryEntry FindUserByCertificate(X509Certificate2 cert)
{
using var searcher = new DirectorySearcher(new DirectoryEntry(_ldapPath));
// Option 1: UPN from SAN
var upn = GetUpnFromCertificate(cert);
if (upn != null)
{
searcher.Filter = $"(userPrincipalName={upn})";
return searcher.FindOne()?.GetDirectoryEntry();
}
// Option 2: altSecurityIdentities (Explicit Mapping)
var issuerSerial = $"X509:{cert.Issuer}{cert.SerialNumber}";
searcher.Filter = $"(altSecurityIdentities={issuerSerial})";
var result = searcher.FindOne();
if (result != null) return result.GetDirectoryEntry();
// Option 3: Subject + Issuer
var subjectIssuer = $"X509:{cert.Subject}{cert.Issuer}";
searcher.Filter = $"(altSecurityIdentities={subjectIssuer})";
return searcher.FindOne()?.GetDirectoryEntry();
}
public void MapCertificateToUser(string userDn, X509Certificate2 cert, CertMappingType type)
{
using var user = new DirectoryEntry($"{_ldapPath}/{userDn}");
string mapping = type switch
{
CertMappingType.IssuerSerial =>
$"X509:{cert.Issuer}{cert.SerialNumber}",
CertMappingType.SubjectIssuer =>
$"X509:{cert.Subject}{cert.Issuer}",
CertMappingType.SubjectOnly =>
$"X509:{cert.Subject}",
CertMappingType.IssuerSubjectHash =>
$"X509:{cert.GetCertHashString()}",
_ => throw new ArgumentException("Unknown mapping type")
};
var altSecIds = user.Properties["altSecurityIdentities"];
altSecIds.Add(mapping);
user.CommitChanges();
Console.WriteLine($"Mapping created: {mapping}");
}
private string GetUpnFromCertificate(X509Certificate2 cert)
{
// UPN OID: 1.3.6.1.4.1.311.20.2.3
var sanExt = cert.Extensions["2.5.29.17"];
if (sanExt == null) return null;
// Parse SAN for UPN...
return null;
}
}
public enum CertMappingType
{
IssuerSerial,
SubjectIssuer,
SubjectOnly,
IssuerSubjectHash
}
----
===== Azure AD Certificate-Based Auth =====
// Azure AD App Registration for CBA
public class AzureAdCbaService
{
private readonly IConfidentialClientApplication _app;
public AzureAdCbaService(string tenantId, string clientId, X509Certificate2 clientCert)
{
_app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.WithCertificate(clientCert) // Client certificate instead of secret
.Build();
}
public async Task AcquireTokenForClient(string[] scopes)
{
return await _app.AcquireTokenForClient(scopes).ExecuteAsync();
}
public async Task AcquireTokenOnBehalfOf(
string[] scopes,
string userToken)
{
var userAssertion = new UserAssertion(userToken);
return await _app.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync();
}
}
// Azure AD B2C with certificate auth
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
----
===== Keycloak X.509 Authenticator =====
{
"alias": "x509-auth",
"providerId": "auth-x509-client-username-form",
"enabled": true,
"config": {
"x509-cert-auth.mapping-source-selection": "Subject's Common Name",
"x509-cert-auth.regular-expression": "CN=(.*?)(?:,|$)",
"x509-cert-auth.user-mapping-method": "Username or Email",
"x509-cert-auth.crl-checking-enabled": "true",
"x509-cert-auth.ocsp-checking-enabled": "true",
"x509-cert-auth.ocsp-responder-uri": "http://ocsp.example.com",
"x509-cert-auth.timestamp-validation-enabled": "true",
"x509-cert-auth.keyusage": "digitalSignature",
"x509-cert-auth.extendedkeyusage": "id-kp-clientAuth"
}
}
----
===== Industry-Specific SSO Scenarios =====
^ Industry ^ IdP ^ Protocol ^ Certificate Requirement ^
| **Enterprise** | Azure AD / Okta | OIDC | UPN in SAN |
| **Government** | ADFS / PIV | SAML HoK | PIV Auth Cert |
| **Healthcare** | gematik IdP | OIDC | HBA/SMC-B |
| **Financial** | Own PKI | SAML | Qualified |
----
===== Related Scenarios =====
^ Relationship ^ Scenario ^ Description ^
| **Prerequisite** | [[.:mtls_client_auth|9.1 mTLS Client Auth]] | TLS level |
| **Related** | [[.:smartcard_login|9.2 Smartcard Login]] | Hardware auth |
| **Related** | [[en:int:pqcrypt:szenarien:zertifikate:client_cert|3.2 Client Certificate]] | Create cert |
----
<< [[.:smartcard_login|<- 9.2 Smartcard Login]] | [[.:start|^ Authentication Overview]] | [[en:int:pqcrypt:szenarien:start|-> All Scenarios]] >>
{{tag>scenario authentication sso saml oidc azure-ad}}
----
//Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//