Delegated access and sign-in solve different problems
OAuth 2.0 lets a client obtain limited access to an API. OpenID Connect adds a standardized identity result for signing a user in. An access token is intended for its resource server; an ID token is intended for its client. Swapping the two because both happen to be JWTs is a design error.
For a hypothetical photo-printing application, permission to read a photo album does not establish which local account should receive the print order. Model the sign-in decision and album permission separately. A user may revoke the album permission while keeping an application account.
Identify the participants first
| Participant | Responsibility in the photo-printing example |
|---|---|
| Resource owner | The person granting access to an album |
| Client | The photo-printing application |
| Authorization server | Authenticates the person and issues the delegated credentials |
| Resource server | The photo API that checks the access token and scopes |
| OIDC relying party | The application validating the provider’s identity assertion |
A confidential backend can protect credentials. JavaScript delivered to a browser and installed mobile applications cannot keep a shared client secret confidential. Putting a secret in an environment variable that is bundled into browser code does not change that distinction.
The authorization-code flow with PKCE
Use a maintained provider SDK or protocol library for the complete flow. Current OAuth security guidance recommends the code flow with PKCE and advises against returning access tokens directly in an authorization response. PKCE protects the code exchange; it is not a replacement for redirect validation, transaction binding or application session security. OAuth Security BCP, RFC 9700.
The provider redirect URI must match the registered URI. Bind each pending transaction to the initiating browser session, expire it, and consume it once. Keep separate pending transactions if multiple tabs can start sign-in; one global verifier can be overwritten by a second tab.
A bounded example: generating an S256 challenge
This browser JavaScript runs in a secure context and only demonstrates PKCE generation. It does not perform sign-in or validate a callback. The verifier must remain available only to the pending transaction; never place it in the authorization URL.
function base64url(bytes) {
return btoa(String.fromCharCode(...bytes))
.replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
}
async function createPkce() {
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
const digest = await crypto.subtle.digest(
'SHA-256', new TextEncoder().encode(verifier)
);
return {
verifier,
challenge: base64url(new Uint8Array(digest)),
method: 'S256'
};
}
The challenge is a base64url-encoded SHA-256 digest of the verifier, as defined in PKCE, RFC 7636. On a backend, use that runtime’s cryptographic API; do not mix Node’s crypto.randomBytes with browser-only storage APIs in one purportedly runnable example.
Validate identity before creating a session
Decoding a JWT only reads its contents. Validation must establish that a trusted issuer produced it for this client and this sign-in transaction. Check the signature using trusted issuer metadata, the allowed algorithm, issuer, audience, expiry and other required claims. If a nonce was sent, check the matching nonce. Multiple audiences can require an authorized-party check. Use the issuer and subject together as the external identity key; an email address can change and is not a universal account identifier. OIDC Core: ID token validation.
Do not fetch a key URL supplied by an untrusted token header. Configure the issuer and discover its keys through the library’s trusted metadata handling. Key rotation, time skew and provider-specific client rules are reasons to use that library rather than assembling a verifier from a few snippets.
Tokens have different lifecycles
| Credential | Where it is used | What expires or invalidates it |
|---|---|---|
| Access token | Resource API | Issuer-defined expiry, revocation or API policy |
| Refresh token | Authorization server | Rotation, revocation, expiry or provider policy |
| ID token | Client sign-in validation | Contains an expiry; not a permanent authentication credential |
| Application session | Your application | Your session lifetime, logout and account policy |
One hour is an example access-token lifetime, not an OAuth requirement. A provider may not issue a refresh token, and it may not rotate one on every exchange. When a new refresh token is issued, update stored credentials atomically. Concurrent refresh attempts need coordination so an older response does not overwrite a newer credential.
For a backend-for-frontend design, keep provider tokens on the server and send the browser a session identifier in a Secure, HttpOnly cookie. Select SameSite behavior compatible with the redirect flow and retain CSRF protection for application mutations. HttpOnly reduces JavaScript token extraction; it does not make XSS harmless.
Choose the grant for the actor
- A person signing in to a web or mobile application needs a user authorization flow, usually authorization code with PKCE.
- A service acting under its own identity can use client credentials when supported. There is no end user to infer from that token.
- A background task acting for a person needs an explicit delegated permission and an appropriate refresh-token policy.
Request the smallest necessary scopes. Authentication to a user account does not authorize every object the account can name: resource-level authorization still belongs in the API.
Worked failure: a valid token for the wrong application
Assume two applications trust the same provider. A token’s signature can be valid while its audience names the other application. Accepting it would confuse “the provider issued this” with “the provider issued this for us.” Reject it before account lookup, and test the failure explicitly. Also test a missing nonce, an expired token, a callback with no stored transaction and a replayed callback.
A second test concerns two browser tabs. Start sign-in in both, finish the first, then the second. Each should match its own transaction or fail cleanly. Neither should silently attach the wrong identity to an existing session.
Related Concepts
- API Gateway — token enforcement at the edge
- Proxies — intermediary trust boundaries
- WebSockets — authentication for persistent connections
Keep reading
DNS Architecture
The phonebook of the internet. How Domain Name System works, the DNS hierarchy, and recursive vs iterative resolution strategies.
HTTP Evolution (H1 to H3)
From text-based HTTP/1.1 to binary HTTP/2 and UDP-based HTTP/3 (QUIC). Why we needed upgrades and how they solve Head-of-Line Blocking.
Proxies: Forward vs Reverse
Understanding proxy servers that act as intermediaries between clients and servers, including forward proxies for controlled client egress and reverse proxies for load balancing, security, and caching.
TCP Handshake & Congestion
The 3-way handshake that powers the internet. SYN, SYN-ACK, ACK. Flow control vs Congestion control, and modern algorithms like BBR.
Tor Network (Onion Routing)
How onion routing separates network knowledge across relays, how onion services differ from ordinary websites, and what the design does not protect.
WebRTC (Peer-to-Peer Communication)
Browser APIs and protocols for real-time audio, video and data, with signaling, ICE connectivity checks, TURN relay fallback and multiparty media-server tradeoffs.