OAuth2 Authentication Architecture: Grant Types, JWT Tokens & SSO
OAuth2 authentication is the backbone of modern authorization on the web. Every time you click "Sign in with Google" or grant a third-party app access to your calendar, OAuth2 is doing the heavy lifting behind the scenes.
This guide covers the full OAuth2 authentication architecture — grant types, token mechanics, SSO with OpenID Connect, secure storage patterns, and how it all compares to API keys and session-based auth.
OAuth2 Grant Types#
OAuth2 defines several flows (grant types), each designed for a different context.
Authorization Code Flow#
The most common OAuth2 flow for server-side web apps. The client redirects the user to the authorization server, which returns an authorization code. The server exchanges that code for tokens.
User → App → Auth Server (login + consent)
Auth Server → App (authorization code)
App Server → Auth Server (code + client_secret → tokens)
This keeps the client_secret on the server, never exposed to the browser.
Authorization Code with PKCE#
For single-page apps and mobile apps where you cannot store a client_secret safely. PKCE (Proof Key for Code Exchange) adds a dynamically generated code_verifier and code_challenge to prevent authorization code interception.
// Generate PKCE challenge
const codeVerifier = generateRandomString(64);
const codeChallenge = base64url(sha256(codeVerifier));
// Authorization request includes code_challenge
const authUrl = `https://auth.example.com/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://app.example.com/callback&
code_challenge=${codeChallenge}&
code_challenge_method=S256&
scope=openid profile email`;
Client Credentials Flow#
Machine-to-machine communication where no user is involved. The client authenticates directly with its own credentials.
const response = await fetch("https://auth.example.com/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: SERVICE_CLIENT_ID,
client_secret: SERVICE_CLIENT_SECRET,
scope: "read:metrics write:logs",
}),
});
Device Authorization Flow#
For devices with limited input (smart TVs, CLI tools). The device displays a code, and the user authorizes on a separate device with a browser.
Device → Auth Server: request device code
Device → User: "Go to https://auth.example.com/device and enter code: ABCD-1234"
Device polls Auth Server until user completes authorization
Token Types#
OAuth2 issues several token types, each with a distinct role.
| Token | Purpose | Lifetime |
|---|---|---|
| Access token | Authorizes API requests | Minutes (5–60) |
| Refresh token | Obtains new access tokens | Days to months |
| ID token (OIDC) | Carries user identity claims | Minutes |
JWT Structure#
Most OAuth2 implementations use JWTs (JSON Web Tokens) for access and ID tokens. A JWT has three base64url-encoded parts separated by dots:
header.payload.signature
// Header
{ "alg": "RS256", "typ": "JWT", "kid": "key-2026-03" }
// Payload
{
"sub": "user_8291",
"iss": "https://auth.example.com",
"aud": "https://api.example.com",
"exp": 1743206400,
"iat": 1743202800,
"scope": "read:profile write:settings",
"email": "user@example.com"
}
// Signature
RSASHA256(base64url(header) + "." + base64url(payload), privateKey)
Always validate iss, aud, exp, and the signature on every request. Never trust claims without verification.
Token Storage: httpOnly Cookies vs localStorage#
Where you store tokens in the browser has major security implications.
| Approach | XSS Risk | CSRF Risk | Recommendation |
|---|---|---|---|
localStorage | High — JS can read tokens | None | Avoid for sensitive tokens |
httpOnly cookie | Protected — JS cannot access | Moderate | Preferred, with CSRF protection |
| In-memory (JS variable) | Moderate — cleared on refresh | None | Good for short-lived SPAs |
Best practice: Store refresh tokens in httpOnly, Secure, SameSite=Strict cookies. Keep access tokens in memory. Use a silent refresh or rotating refresh tokens to maintain sessions.
// Server sets token cookie
res.cookie("refresh_token", refreshToken, {
httpOnly: true,
secure: true,
sameSite: "strict",
path: "/api/auth/refresh",
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
});
SSO with OpenID Connect (OIDC)#
OpenID Connect is an identity layer on top of OAuth2. It adds the id_token and a standard /userinfo endpoint, making SSO implementation straightforward.
App A → Auth Server: authorize (scope: openid profile)
User logs in once
Auth Server → App A: id_token + access_token
...
App B → Auth Server: authorize (scope: openid email)
Auth Server recognizes session → skips login
Auth Server → App B: id_token + access_token
Key OIDC concepts:
- Discovery document at
/.well-known/openid-configurationpublishes endpoints and supported features. - ID token contains identity claims (
sub,email,name) — verified by signature, not by calling an API. - Scopes like
openid,profile,emailcontrol which claims appear in the token.
OAuth2 vs API Keys vs Session Auth#
| Factor | OAuth2 | API Keys | Session Auth |
|---|---|---|---|
| Delegation | Users grant scoped access to third parties | Full access, no delegation | No delegation |
| Granularity | Fine-grained scopes | All-or-nothing | Tied to user session |
| Revocation | Per-token, per-app | Rotate entire key | Destroy session |
| Best for | Third-party integrations, SSO | Server-to-server, internal tools | Traditional server-rendered apps |
Use OAuth2 when you need delegated, scoped access. Use API keys for simple internal service communication. Use session auth for monolithic apps with server-rendered pages.
Security Best Practices#
- Always use PKCE — even for confidential clients, PKCE adds defense in depth.
- Short-lived access tokens — 5 to 15 minutes. Use refresh tokens to renew.
- Rotate refresh tokens — issue a new refresh token with each use and invalidate the old one.
- Validate the
stateparameter — prevents CSRF on the authorization callback. - Restrict redirect URIs — register exact URIs, never use wildcards.
- Use
RS256overHS256— asymmetric signing lets resource servers verify tokens without sharing a secret. - Implement token revocation — provide an endpoint and honor revoked tokens promptly.
Common Mistakes#
- Storing tokens in
localStoragewithout understanding XSS exposure. - Not validating JWT claims — trusting the token without checking
iss,aud, andexp. - Using the implicit flow — deprecated in OAuth 2.1. Use authorization code with PKCE instead.
- Long-lived access tokens — if an access token lives for days, a leak becomes catastrophic.
- Ignoring token size — JWTs in headers can exceed proxy or CDN limits. Keep claims lean.
- Skipping PKCE for public clients — leaves the authorization code vulnerable to interception.
When to Choose What#
Need third-party access? → OAuth2 (authorization code + PKCE)
Machine-to-machine? → OAuth2 (client credentials) or mutual TLS
Internal service, simple auth? → API key with IP allowlist
Server-rendered monolith? → Session cookies
Need SSO across apps? → OAuth2 + OpenID Connect
OAuth2 authentication architecture is not just about login buttons. It is a framework for secure, scoped, revocable delegation — and understanding its moving parts is what separates a working integration from a secure one.
Build smarter systems at codelit.io.
131 articles on system design at codelit.io/blog.
Try it on Codelit
Chaos Mode
Simulate node failures and watch cascading impact across your architecture
Related articles
Try these templates
Build this architecture
Generate an interactive OAuth2 Authentication Architecture in seconds.
Try it in Codelit →
Comments