SSO Starts Here
Every employee knows the feeling: one click on “Sign in with Google” or “Sign in with Okta” and you’re into Slack, Jira, and GitHub without typing a password again. That seamless experience is called Single Sign-On (SSO), and it runs on two protocols working together:
- OAuth 2.0 handles authorisation — “This app can read your profile”
- OpenID Connect (OIDC) handles authentication — “You are alice@corp.com”
OIDC is a thin identity layer built on top of OAuth 2.0, defined by the OpenID Foundation. Together, they are the standard backbone for enterprise SSO.
How SSO Works: The User’s Experience
sequenceDiagram
participant User
participant Slack
participant Jira
participant IdP as "Corp IdP (Okta / Azure AD)"
User->>Slack: Open Slack
Slack->>IdP: Redirect to /authorize
IdP->>User: Enter credentials
User->>IdP: Authenticate
IdP->>IdP: Set SSO session cookie
IdP->>Slack: Redirect back with auth code
Slack->>IdP: Exchange code for tokens
IdP->>Slack: id_token + access_token
Slack->>User: Welcome (signed in)
User->>Jira: Open Jira
Jira->>IdP: Redirect to /authorize
IdP->>IdP: SSO session still valid (cookie)
IdP->>Jira: Redirect back (no password needed)
Jira->>IdP: Exchange code for tokens
IdP->>Jira: id_token + access_token
Jira->>User: Welcome (auto signed in)
The user authenticates once with the corporate Identity Provider (IdP) — Okta, Azure AD, Keycloak, or any OIDC-compliant provider. The IdP sets a session cookie. Every subsequent app redirects to the IdP, which recognises the existing session and issues tokens without prompting for credentials again.
OAuth 2.0: Delegated Authorisation
Before OAuth, sharing access meant handing over your password — the “valet key” problem. OAuth solves this by letting a user grant an app limited, revocable access to specific resources, without sharing credentials. Defined in RFC 6749 and RFC 6750.
The Four Actors
Every OAuth SSO flow has the same four roles (RFC 6749 §1.1):
| Actor | In SSO Terms |
|---|---|
| Resource Owner | The employee (owns their identity data) |
| Client | The app requesting SSO access (Slack, Jira, etc.) |
| Authorisation Server | The corporate IdP (Okta, Azure AD, Keycloak) |
| Resource Server | The API behind the app that validates tokens |
Authorisation Code + PKCE Flow
The most common SSO grant type. PKCE (RFC 7636) binds the authorisation code to the client that started the flow, preventing interception attacks even without a client secret.
sequenceDiagram
participant User
participant Client
participant AS as IdP (Authorisation Server)
participant RS as App API
User->>Client: Click "Sign in with SSO"
Client->>Client: Generate code_verifier + SHA-256 → code_challenge
Client->>AS: GET /authorize?response_type=code&code_challenge=S256...
AS->>User: Login prompt (if no SSO session)
User->>AS: Authenticate + approve scopes
AS->>Client: 302 redirect → ?code=auth_code&state=xyz
Client->>AS: POST /token?code=auth_code&code_verifier=...
AS->>AS: SHA-256(verifier) == stored challenge?
AS->>Client: { access_token, refresh_token, id_token }
Client->>RS: GET /api/data (Authorization: Bearer access_token)
RS->>Client: { protected resource }
Grant Types
| Grant | SSO Use Case | Security |
|---|---|---|
| Authorisation Code + PKCE | Web apps, SPAs, mobile SSO — the modern standard | Highest. PKCE prevents code interception. Required by RFC 9700. |
| Client Credentials | Server-to-server SSO (service accounts, cron jobs) | No user involved. Secrets must be rotated. |
| Device Code | CLI tools, smart TVs (SSO on headless devices) | User authorises on a separate device. RFC 8628. |
| Refresh Token | Long-lived SSO sessions without re-auth | Rotatable. Store securely; never expose to browser. |
PKCE in Detail
PKCE protects SSO flows in public clients (SPAs, mobile apps) where a client secret cannot be stored securely:
- Client generates a random
code_verifier(43–128 characters, RFC 7636 §4.1) - Client computes
code_challenge = BASE64URL(SHA-256(code_verifier))(S256 method) - Client sends
code_challenge+code_challenge_method=S256with the/authorizerequest - After receiving the authorisation code, the client sends the raw
code_verifierat the/tokenendpoint - Server recomputes
SHA-256(code_verifier)and rejects the request unless it matches the stored challenge
Scopes
Scopes define what each app can access in an SSO ecosystem: openid, profile, email, groups. They are a bounded, user-visible permission model. When an employee approves “Slack wants to read your profile and group membership”, that is scope consent.
OpenID Connect: How SSO Knows Who You Are
OAuth alone can tell an app “this token is valid”, but it cannot say who the user is. OIDC fills that gap. It adds (Core 1.0):
- ID Token — A signed JWT with identity claims (
sub,email,name,groups) - UserInfo Endpoint — Fetch additional claims using the access token
- Discovery URL —
https://idp.corp.com/.well-known/openid-configuration— standardised provider metadata per RFC 8414
Standard OIDC Scopes and Claims
| Scope | Standard Claims |
|---|---|
openid | sub (required — unique user identifier) |
profile | name, given_name, family_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, updated_at |
email | email, email_verified |
address | address (formatted, street_address, locality, region, postal_code, country) |
phone | phone_number, phone_number_verified |
The ID Token
When a user SSOs into an app, the IdP returns an ID token like this:
{
"iss": "https://idp.corp.com",
"sub": "a1b2c3d4",
"aud": "slack-app-id",
"exp": 1718123456,
"iat": 1718119856,
"email": "alice@corp.com",
"email_verified": true,
"groups": ["engineering", "prod-access"],
"nonce": "n-0S6_WzA2Mj"
}
Signed by the IdP (RS256, ES256). The app verifies the signature using the IdP’s JWKS endpoint (/.well-known/jwks.json). The sub claim is the persistent user identifier across all SSO apps — the IdP guarantees it never changes for that user.
Token Types in SSO
| Token | Format | Lifespan | SSO Role |
|---|---|---|---|
| Access Token | Opaque or JWT | Minutes | Authorises API calls on behalf of the user |
| Refresh Token | Opaque | Days/months | Obtains new access tokens without re-prompting |
| ID Token | JWT only | Minutes | Proves who the user is and when they authenticated |
SSO Session Management
After the initial authentication, the IdP maintains a session (usually via a cookie on the IdP domain). When the user opens a new SSO-linked app:
- The new app redirects to the IdP’s
/authorizeendpoint - The IdP detects the existing session cookie
- The IdP immediately redirects back with an authorisation code — no password prompt
- The app exchanges the code for tokens and signs the user in
This is silent authentication — the user clicks once and is signed into every app without further prompts. If the IdP session expires, the user is prompted once and all apps regain access on next redirect.
Security Considerations for SSO
- Always use PKCE with authorisation code flow — prevents code interception attacks even without a client secret. Required by OAuth 2.1 and RFC 9700 for all public clients.
- Validate the
iss(issuer) claim — ensure the token comes from your corporate IdP, not a rogue provider - Use
noncein OIDC — prevents replay attacks on the ID token - Validate token signatures — fetch the IdP’s JWKS and verify every token
- Short token lifetimes — access tokens in minutes, refresh tokens with rotation. If a device is lost, short-lived tokens limit exposure
- Never expose access tokens to browser JavaScript for confidential clients — they are backend-to-backend credentials
- Use PAR (Pushed Authorisation Requests, RFC 9126) — sends the authorisation request body via a back-channel, hiding parameters from the browser URL
- Use JARM (JWT Secured Authorisation Response Mode, RFC 9902) — signs the authorisation response to prevent tampering
- Bind tokens to the TLS session via
s_hashor DPoP (RFC 9449) to prevent token export and replay
Why Every Enterprise Standardises on OIDC for SSO
- Single Sign-On (SSO) — One login, all apps. The IdP session eliminates password fatigue.
- Federated identity — Authenticate against any standards-compliant IdP (Azure AD, Okta, Keycloak, Google). No vendor lock-in.
- Fine-grained access — Scopes and claims let IT control exactly what each app can see and do.
- Centralised audit trail — The IdP logs every authentication event across every app in one place.
- Zero-trust ready — Short-lived tokens replace long-lived VPNs. Every request carries verifiable proof of identity.
- Just-in-time provisioning — Apps receive user attributes (name, email, groups) via OIDC claims on first SSO, enabling automatic account creation.
OIDC is the authentication layer behind Kubernetes clusters (kube-apiserver + OIDC), SCIM provisioning (SCIM uses OAuth for API security), and FIDO2 passkey authentication.
OAuth 2.0 and OIDC are the foundation of modern identity. Every SaaS app, every enterprise portal, and every zero-trust architecture relies on them. Understanding the flow — tokens, scopes, grants, and signatures — is table stakes for anyone building secure software.
References
- RFC 6749 — OAuth 2.0 Authorization Framework
- RFC 6750 — OAuth 2.0 Bearer Token Usage
- RFC 7636 — Proof Key for Code Exchange (PKCE)
- RFC 7519 — JSON Web Token (JWT)
- RFC 7517 — JSON Web Key (JWK)
- RFC 8414 — OAuth 2.0 Authorization Server Metadata
- RFC 8628 — OAuth 2.0 Device Authorization Grant
- RFC 9126 — OAuth 2.0 Pushed Authorization Requests (PAR)
- RFC 9449 — OAuth 2.0 Demonstration of Proof of Possession (DPoP)
- RFC 9700 — OAuth 2.0 Security Best Current Practice
- RFC 9902 — JWT-Secured Authorization Response Mode (JARM)
- OpenID Connect Core 1.0
- OpenID Connect Discovery (RFC 8414)
- Kubernetes OIDC Integration — OIDC as auth layer for K8s clusters
- SCIM Identity Provisioning — SCIM uses OAuth for API security