SSO (Single Sign-On)

Intro

Single Sign-On (SSO) lets a user authenticate once with a central Identity Provider (IdP) and then access multiple applications without re-entering credentials. The IdP issues tokens or assertions that each application validates independently. In modern systems, SSO is implemented with OpenID Connect (OIDC) on top of OAuth 2.0. The core engineering work is session management, token validation, and handling failure modes safely.

See OAuth OIDC for the underlying protocol details.

How It Works — OIDC Flow

sequenceDiagram
  participant Browser
  participant App as Application (Relying Party)
  participant IdP as Identity Provider

  Browser->>App: Access protected resource
  App->>Browser: Redirect to IdP (with client_id, redirect_uri, state, nonce)
  Browser->>IdP: User authenticates
  IdP->>Browser: Redirect back with authorization code
  Browser->>App: Send authorization code
  App->>IdP: Exchange code for ID token + access token
  IdP->>App: Tokens (signed JWT)
  App->>App: Validate ID token (signature, issuer, audience, expiry, nonce)
  App->>Browser: Create application session (cookie)

Key points:

SAML vs OIDC

Aspect SAML 2.0 OIDC (OAuth 2.0)
Format XML assertions JSON Web Tokens (JWT)
Transport HTTP POST/Redirect HTTP redirects + REST
Age 2005 (enterprise legacy) 2014 (modern web/mobile)
Mobile support Poor (XML, browser-only) Excellent (native apps, SPAs)
Complexity High (XML signatures, metadata) Lower (JSON, standard libraries)
Adoption Enterprise (Okta, ADFS, Salesforce) Cloud-native (Azure AD, Google, GitHub)

Decision rule: use OIDC for new systems. Use SAML only when integrating with legacy enterprise IdPs that don't support OIDC (e.g., older ADFS deployments, some SaaS products).

// Program.cs — configure OIDC with Azure AD
builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
    options.Authority = "https://login.microsoftonline.com/{tenant-id}/v2.0";
    options.ClientId = builder.Configuration["AzureAd:ClientId"];
    options.ClientSecret = builder.Configuration["AzureAd:ClientSecret"];
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add("openid");
    options.Scope.Add("profile");
    options.Scope.Add("email");
});

The middleware handles the redirect, code exchange, token validation, and cookie creation automatically.

Token Validation Checklist

When accepting an ID token from an IdP, validate:

  1. Signature — verify using the IdP's public key (fetched from /.well-known/openid-configuration).
  2. Issuer (iss) — must match the expected IdP URL.
  3. Audience (aud) — must include your application's client_id.
  4. Expiry (exp) — token must not be expired.
  5. Nonce — must match the nonce sent in the authorization request (prevents replay).
  6. Redirect URI — must match the registered redirect URI exactly.

Pitfalls

Shared session vs federated identity
SSO does not mean all applications share one session cookie. Each application creates its own session after validating the IdP token. If the IdP session expires, users may need to re-authenticate — but this is transparent if the application handles token refresh.

Single point of failure
If the IdP is unavailable, users cannot authenticate to any SSO-protected application. Mitigate with IdP high availability (Azure AD, Okta have SLAs) and graceful degradation for non-critical paths.

Token leakage
ID tokens contain user claims (email, name, roles). Logging tokens or passing them in URLs exposes PII. Always transmit tokens over HTTPS and store them in secure, HttpOnly cookies.

Logout complexity
Logging out of one application does not automatically log out of the IdP or other applications. Implement front-channel logout (IdP notifies all applications) or back-channel logout (server-to-server) for complete logout.

Questions

References


Whats next