Scenario 9.3: SSO Integration

Category: 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<ClaimsPrincipal> 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:<I>{cert.Issuer}<S>{cert.SerialNumber}";
        searcher.Filter = $"(altSecurityIdentities={issuerSerial})";
        var result = searcher.FindOne();
        if (result != null) return result.GetDirectoryEntry();
 
        // Option 3: Subject + Issuer
        var subjectIssuer = $"X509:<S>{cert.Subject}<I>{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:<I>{cert.Issuer}<SR>{cert.SerialNumber}",
            CertMappingType.SubjectIssuer =>
                $"X509:<S>{cert.Subject}<I>{cert.Issuer}",
            CertMappingType.SubjectOnly =>
                $"X509:<S>{cert.Subject}",
            CertMappingType.IssuerSubjectHash =>
                $"X509:<SHA1-PUKEY>{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<AuthenticationResult> AcquireTokenForClient(string[] scopes)
    {
        return await _app.AcquireTokenForClient(scopes).ExecuteAsync();
    }
 
    public async Task<AuthenticationResult> 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

Relationship Scenario Description
Prerequisite 9.1 mTLS Client Auth TLS level
Related 9.2 Smartcard Login Hardware auth
Related 3.2 Client Certificate Create cert

« <- 9.2 Smartcard Login | ^ Authentication Overview | -> All Scenarios »


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional

Zuletzt geändert: on 2026/01/30 at 12:21 AM