Signing in once with SSO
Open in the interactive app: practice, quiz, mock interview →In one minute
- SSO lets a user authenticate once at an identity provider (IdP) and then access many applications (service providers, SPs) without logging in again.
- It works because the IdP keeps its own session; each app redirects to the IdP, which silently issues a signed assertion for that app.
- The two dominant protocols are SAML 2.0 (XML assertions, enterprise) and OpenID Connect (JWT ID tokens on top of OAuth 2.0, modern apps).
- Every app still has its own local session; SSO only automates how that session gets created.
- Logout, session lifetime and account provisioning are the hard parts, not login.
How it works
Three sessions, not one
After SSO, a user has an IdP session (a cookie on the IdP's domain) and one local session per app (cookies on each app's domain). Browsers do not share cookies across domains, so the apps never see the IdP cookie. Instead each app sends the browser to the IdP, the IdP recognises its own cookie, and it sends the browser back with a signed statement of who the user is.
SP-initiated login with OIDC
- The user opens
crm.example.comwith no session. The app redirects to the IdP's authorize endpoint withscope=openid, anonceandstate. - If the IdP session exists, there is no login prompt; the IdP immediately redirects back with an authorization code. If not, it authenticates the user (password, MFA, passkey) first.
- The app exchanges the code for an ID token, a JWT with
iss,sub(stable user id),aud(this app),exp,nonceandauth_time. - The app validates signature, issuer, audience, expiry and nonce, then creates its own session (OIDC Core §3.1.3.7).
OIDC Core and eight companion specs were also published as ISO/IEC standards in October 2024 (Core is ISO/IEC 26131:2024), which matters in procurement where only treaty-body standards count (OpenID Foundation).
SAML follows the same shape with different encoding: the IdP posts a signed XML Response containing an Assertion to the SP's assertion consumer service URL, with Audience, NotBefore and NotOnOrAfter conditions. Its XML signature handling is error-prone, one reason new systems prefer OIDC.
Logout
Logging out of one app ends only that app's session. Single logout needs the IdP to tell every app. Front-channel logout loads each app's logout URL in hidden frames, and those frames need the app's cookie in a third-party context. Safari has blocked third-party cookies by default since March 2020 (WebKit), while Chrome dropped its deprecation plan in April 2025 and kept them under user control (Google), so the same logout works in one browser and silently fails in another. The front-channel spec itself tells apps to detect this and warn the user (OIDC Front-Channel Logout §4.1). Back-channel logout has the IdP POST a signed logout token directly to each app's server, which then deletes sessions for that sub or session id. This requires apps to index sessions by user, which many do not.
Provisioning
SSO answers "who is this?", not "does this person have an account here, and in which groups?". Just-in-time provisioning creates the account on first login from token claims. SCIM lets the IdP push creates, updates and, crucially, deactivations, so a departing employee loses access everywhere even if an app session is still open (RFC 7644). A newer channel goes further: the OpenID Shared Signals Framework and its CAEP profile, final since 2025, let the IdP stream events such as session-revoked and credential-change to apps in near real time (CAEP 1.0).
Down the stack
Layer 1The contract
The IdP asserts, with its signature, that a subject authenticated at a given time, for one named audience, valid within a short window. The SP promises to create a session only from an assertion that is correctly signed by the expected issuer, addressed to itself, within its time window (plus a small clock skew), and not seen before. Login is therefore strongly specified. Logout is not: front-channel logout is best effort, and only back-channel logout or deprovisioning gives a server-to-server guarantee that sessions end.
Layer 2The mechanism
In SAML, the SP sends an AuthnRequest, deflated and base64-encoded into a query string (HTTP-Redirect binding, which every endpoint must support with DEFLATE (SAML Bindings §3.4.4)). The IdP answers with an auto-submitting HTML form that POSTs a base64 SAMLResponse to the SP's assertion consumer service URL (HTTP-POST binding). Inside, the Assertion carries Issuer, Subject/NameID, Conditions with NotBefore, NotOnOrAfter and AudienceRestriction, an AuthnStatement, attributes, and InResponseTo linking it to the request. The XML signature uses exclusive canonicalisation and a Reference URI pointing at the signed element's ID; signature wrapping attacks exploit code that verifies one element but reads another. A 2025 variant hit ruby-saml, which parsed the same response with two XML parsers that disagreed about which Signature element counted (see In the wild). OIDC replaces all this with a JWT id_token and a discovery document at /.well-known/openid-configuration. Back-channel logout POSTs a signed logout token with an events claim and a sid identifying the IdP session, or a sub, or both. The token must not carry a nonce, so it can never pass as an ID token, and the app answers 200 or 400 (OIDC Back-Channel Logout §2.4).
Layer 3The machine
A SAML response is often 5 to 15 KB once base64-encoded, too big for a URL (many servers cap request lines near 8 KB), which is why responses are POSTed while requests are deflated. Parsing, canonicalising and verifying XML costs milliseconds and needs a parser with external entities disabled. Every silent SSO hop is a chain of full browser navigations: two redirects, possibly a fresh TLS handshake to the IdP, and a token exchange, so 200 to 400 ms on a mobile network. Cookies matter at the byte level too: a browser cookie is limited to about 4 KB, and SameSite=Lax cookies are not sent on the cross-site POST back from the IdP, so an SP that keeps its pre-login state in such a cookie loses it unless it marks that cookie SameSite=None; Secure.
Why it's built this way
- Browsers isolate cookies per site, so apps cannot share a session and must bounce through the IdP.
- The browser is an untrusted courier between IdP and app, so assertions are signed, audience-bound, time-limited and replay-checked.
- Enterprises standardised on XML web services in the mid-2000s, giving SAML; the JSON and mobile era layered OIDC onto OAuth.
- Apps keep their own sessions for speed and to survive IdP blips, which turns revocation into a distributed problem solved by back-channel logout and SCIM.
- Centralising identity makes the IdP the single gate to everything, so it must be run as tier-zero infrastructure.
Diagram
sequenceDiagram
participant B as Browser
participant A1 as "App 1 (mail)"
participant A2 as "App 2 (crm)"
participant I as "Identity provider"
B->>A1: GET /inbox
A1-->>B: redirect to IdP
B->>I: authorize (no IdP cookie)
I-->>B: login + MFA, set IdP cookie
B->>A1: callback with code, app session created
B->>A2: GET /deals
A2-->>B: redirect to IdP
B->>I: authorize (IdP cookie present)
I-->>B: redirect with code, no prompt
B->>A2: callback, app session createdWorked example
A company of 8,000 employees uses 60 internal and SaaS applications behind one IdP.
Logins: each employee starts the day with one interactive login (password plus push MFA), then touches about 12 apps. That is 8,000 interactive logins and about 96,000 silent SSO redirects per day, peaking around 9 a.m. at perhaps 150 per second. Each silent redirect costs the user about 300 ms (two redirects and a token exchange), which is why apps keep local sessions for hours rather than re-checking every page.
Session lifetimes: IdP session 10 hours, app sessions 8 hours, ID token 5 minutes (used only to establish the session). If HR deactivates a user at 14:00, the IdP blocks new logins immediately, but open app sessions survive until they expire, up to 8 hours later. With SCIM deactivation pushed to the 25 apps that support it, and back-channel logout to the 30 that support that, exposure drops to the 5 apps that support neither, which the security team either replaces or puts behind a proxy that checks the IdP session every 15 minutes.
Availability: the IdP is now on the critical path for every new session. A 30-minute IdP outage at 9 a.m. blocks everyone who has not yet logged in, so the IdP needs multi-region deployment and its own incident runbook. Azure's Federated Identity pattern makes the same point: authentication becomes a single point of failure, so deploy identity in the same regions as the apps (Azure Architecture Center).
In the wild
- Cloudflare, 2025: the identity store went down and Access failed closed. A third-party storage provider behind Workers KV failed, and because Cloudflare Access reads application, policy and identity data from KV, 100% of identity-based Access logins failed from 17:52 to 20:28 UTC. Access is designed to deny when it cannot decide, so the outage blocked logins rather than letting everyone in. Service-token, mTLS and IP-based access kept working. Cloudflare is removing KV's dependence on a single backing store (Cloudflare).
- ruby-saml and GitLab, 2025: sign in as anyone. ruby-saml up to 1.17.0 parsed SAML responses with both REXML and Nokogiri, and crafted XML made them pick different
Signatureelements, disconnecting the digest check from the signature check. One validly signed document from the target IdP, such as another user's assertion, was enough to forge assertions for any user. Reported in November 2024, fixed in 1.18.0 on 12 March 2025, with an exploitable GitLab instance found and a coordinated release planned (GitHub Security Lab).
Trade-offs
| Option | Strengths | Weaknesses | Choose when |
|---|---|---|---|
| OIDC | JSON/JWT, mobile-friendly, simpler libraries | Fewer legacy enterprise integrations | New apps, consumer and workforce |
| SAML 2.0 | Universal in enterprise SaaS | XML signatures, complex, browser-only | Integrating with enterprise customers and legacy SaaS |
| Long app sessions | Fewer redirects, resilient to IdP blips | Slow revocation | Low-risk internal tools |
| Short app sessions | Fast revocation | More IdP load and latency | Admin and finance systems |
| Back-channel logout | Reliable, server-to-server | Apps must index sessions by user | Workforce SSO with offboarding risk |
Pitfalls in production
- Accepting assertions for another app. Skipping
audchecks lets a token minted for one SP log into another. Validate audience and issuer always. - XML signature wrapping. Validating one signed element but reading claims from an unsigned one. Use a maintained SAML library, keep it patched (ruby-saml needed critical bypass fixes in 2024 for CVE-2024-45409 and again in March 2025), and never parse assertions by hand (OWASP SAML Security).
- Matching users by email. Emails change and can be reused. Key accounts on the IdP's immutable
subor NameID. - Orphaned sessions after offboarding. Long app sessions outlive deactivation. Use SCIM and back-channel logout, and cap session length.
Leadership lens
- Treat the IdP as tier-zero infrastructure: multi-region, monitored, with break-glass accounts.
- Require SSO plus SCIM in vendor evaluations; "SSO tax" pricing tiers are a real budget line.
- Ask in review: when a user is deactivated, how many minutes until every session in this app is dead?
- Standardise on one protocol and library per stack so validation is done once and done right.
Check yourself
Why does the second app not show a login prompt?
Show answer
The browser still holds the IdP's session cookie, so when the second app redirects there the IdP recognises the user and immediately issues a new assertion.
Why does disabling a user at the IdP not immediately log them out of apps?
Show answer
Each app has its own local session that does not consult the IdP until it expires, unless the IdP pushes logout or deprovisioning.
Why key users on
subrather than email?Show answer
subis stable and unique per issuer, while emails change and can be reassigned to a different person.
Sources and further study
Primary
- OpenID Connect Front-Channel Logout 1.0: the iframe-based logout and its documented failure under third-party cookie blocking.
- SAML 2.0 Bindings (OASIS): exactly how HTTP-Redirect and HTTP-POST carry requests and responses.
- OpenID CAEP 1.0: session and credential change events pushed from IdP to apps.
- RFC 7643: SCIM Core Schema: the User and Group resources, including the
activeattribute used for deactivation.
Systems docs
- Keycloak Server Administration Guide: SSO protocols: one open-source IdP's OIDC and SAML 2.0 support, configured side by side.
Books
- Building Microservices (2nd ed.), Sam Newman, Ch 11 "Security": common single sign-on implementations and the single sign-on gateway pattern.
- Building Secure and Reliable Systems, Adkins et al. (Google, 2020), Ch 9 "Design for Recovery": emergency access when the normal identity path is down (read free).
Other curricula
- Azure Architecture Center: Federated Identity pattern: SSO as claims-based access with an STS, including home realm discovery and the IdP as a single point of failure.
- OWASP SAML Security Cheat Sheet: validation steps and the signature wrapping family of attacks.
Watch
- What Is Single Sign-on (SSO)? How It Works (ByteByteGo): animated overview of SAML and OIDC flows.
- SSO Explained in 10 Minutes: SAML, OIDC and SCIM (ByteMonk): ties login, protocols and provisioning together.
Free resources
- OpenID Connect Core 1.0: ID token contents and validation rules.
- OpenID Connect Back-Channel Logout: server-to-server logout tokens.
- SAML 2.0 Technical Overview (OASIS): official description of SAML profiles and bindings.
- RFC 7644: SCIM Protocol: the provisioning API used for deactivation.