Backend Engineering / Authentication

OAuth2 / OIDC in Plain English: JWT vs Reference Tokens + Refresh Tokens (Without Confusion)

A production-auth mental model you can use immediately—without drowning in specs.

Written by

Avatar of author
Avatar of author

Production APIs Playbook — Part 2 of 5

If you’ve ever shipped “JWT auth” and still felt unsure, you’re not alone. Most developers learn tokens as a coding trick. Production teams treat auth as a system: identity, sessions, permissions, and safe rotation.

This post explains OAuth2 and OpenID Connect (OIDC) in a way you can actually use, then clarifies the biggest confusion: when to use JWT access tokens vs opaque (reference) tokens, and how refresh tokens fit in.

What OAuth2 and OIDC actually do

OAuth2 is an authorization framework. It answers: “How can an app get permission to call an API on behalf of a user?”
OIDC is an identity layer on top of OAuth2. It answers: “Who is the user?” and provides standardized login (authentication).

A simple mental model:

  • OAuth2 = permission to access resources (scopes)

  • OIDC = login + identity claims (who the user is)

In real products, you almost always use them together.

The 4 pieces you should know (no fluff)

  1. Resource Owner: the user

  2. Client: your app (web/mobile/backend)

  3. Authorization Server: the identity provider (IdP) that issues tokens

  4. Resource Server: your API that accepts tokens

Your API should not “invent identity.” It should validate tokens issued by the authorization server and enforce permissions.

Flows you’ll see in production (and why)

Authorization Code + PKCE (recommended)
This is the standard for SPAs and mobile apps today. The client redirects the user to the IdP login, then exchanges a short-lived code for tokens. PKCE protects the code exchange so it can’t be stolen.

Client Credentials (machine-to-machine)
Used when there’s no user—service A calling service B. Tokens represent the service identity and permissions.

(You can learn more flows later, but these two cover most real systems.)

Access token vs ID token (people mix them up)

Access token:

  • Sent to APIs

  • Represents permissions (scopes/roles)

  • Short-lived

ID token (OIDC):

  • Sent to the client

  • Represents identity (user profile claims like subject, email, name)

  • Not meant for authorizing API requests

Rule: APIs should validate access tokens, not ID tokens.

JWT vs Reference (opaque) access tokens

This decision affects security, scaling, and how fast you can respond to incidents.

JWT access tokens (self-contained)
A JWT is signed and can be validated locally by your API. It can include claims (user id, roles, scopes, expiry).

Pros:

  • Fast validation (no database/network call)

  • Works well for high throughput

  • Good for distributed systems

Cons:

  • Harder to revoke immediately (until expiry)

  • If you put too much data in it, it becomes a “leaky database”

  • If compromised, it’s valid until it expires

Best for:

  • Short-lived access tokens (5–15 minutes)

  • Systems where “revocation by expiry” is acceptable for access tokens

  • Service-to-service calls with strict expiry

Reference (opaque) access tokens (server-side)
An opaque token is just a random string. Your API can’t understand it without calling the authorization server (introspection) or a shared token store.

Pros:

  • Easy immediate revocation

  • Central control (disable user/session instantly)

  • Less data exposure (token reveals nothing)

Cons:

  • Adds a lookup (latency + dependency)

  • Needs caching and high availability

  • Can become a bottleneck if designed poorly

Best for:

  • High security environments

  • Long-lived sessions where instant revocation matters

  • When you want central session management

A practical production pattern that works

Many strong systems combine both:

  • JWT access tokens: very short-lived (5–10 minutes)

  • Refresh tokens: longer-lived (days/weeks) and revocable

  • Optional: a revocation list or token rotation policies for higher security

Refresh tokens (why they exist)

Access tokens should be short-lived. Refresh tokens exist so users don’t have to log in every 10 minutes.

How it works:

  1. User logs in via Authorization Code + PKCE

  2. Client gets an access token (short) + refresh token (long)

  3. When access token expires, client uses refresh token to get a new access token

  4. Refresh token can be revoked (logout, suspicious activity, password change)

Important security rule:
Refresh tokens should be protected more than access tokens.

  • Store them securely (mobile secure storage; web: HttpOnly cookies if possible)

  • Use rotation (every refresh returns a new refresh token and invalidates the old one)

  • Detect reuse (if an old refresh token is used again, assume theft)

What “production auth” really means (the checklist)

If you want to think like a production backend engineer, validate these:

  • Token lifetimes: access short, refresh controlled

  • Revocation plan: how do you log out everywhere instantly?

  • RBAC/Scopes: how do you represent permissions (and keep them stable)?

  • Key rotation: can you rotate signing keys without downtime?

  • Audience/issuer checks: does your API reject tokens not meant for it?

  • Observability: do you log auth failures with trace context (without leaking secrets)?

  • Rate limiting: do you protect login/refresh endpoints?

Common mistakes (and quick fixes)

Mistake: Using ID tokens to call APIs
Fix: Use access tokens for APIs; ID tokens only for client identity.

Mistake: Long-lived JWT access tokens
Fix: Make access tokens short-lived; rely on refresh tokens.

Mistake: Putting sensitive data in JWT claims
Fix: Keep claims minimal; fetch sensitive info server-side when needed.

Mistake: No plan for revocation
Fix: Use short expiries + refresh token rotation and revocation.

What’s next in the series

Part 3 is GraphQL in production: schema design, preventing N+1, caching, and error handling—so your API stays fast and predictable under load.

Production APIs Playbook — Part 2 of 5

If you’ve ever shipped “JWT auth” and still felt unsure, you’re not alone. Most developers learn tokens as a coding trick. Production teams treat auth as a system: identity, sessions, permissions, and safe rotation.

This post explains OAuth2 and OpenID Connect (OIDC) in a way you can actually use, then clarifies the biggest confusion: when to use JWT access tokens vs opaque (reference) tokens, and how refresh tokens fit in.

What OAuth2 and OIDC actually do

OAuth2 is an authorization framework. It answers: “How can an app get permission to call an API on behalf of a user?”
OIDC is an identity layer on top of OAuth2. It answers: “Who is the user?” and provides standardized login (authentication).

A simple mental model:

  • OAuth2 = permission to access resources (scopes)

  • OIDC = login + identity claims (who the user is)

In real products, you almost always use them together.

The 4 pieces you should know (no fluff)

  1. Resource Owner: the user

  2. Client: your app (web/mobile/backend)

  3. Authorization Server: the identity provider (IdP) that issues tokens

  4. Resource Server: your API that accepts tokens

Your API should not “invent identity.” It should validate tokens issued by the authorization server and enforce permissions.

Flows you’ll see in production (and why)

Authorization Code + PKCE (recommended)
This is the standard for SPAs and mobile apps today. The client redirects the user to the IdP login, then exchanges a short-lived code for tokens. PKCE protects the code exchange so it can’t be stolen.

Client Credentials (machine-to-machine)
Used when there’s no user—service A calling service B. Tokens represent the service identity and permissions.

(You can learn more flows later, but these two cover most real systems.)

Access token vs ID token (people mix them up)

Access token:

  • Sent to APIs

  • Represents permissions (scopes/roles)

  • Short-lived

ID token (OIDC):

  • Sent to the client

  • Represents identity (user profile claims like subject, email, name)

  • Not meant for authorizing API requests

Rule: APIs should validate access tokens, not ID tokens.

JWT vs Reference (opaque) access tokens

This decision affects security, scaling, and how fast you can respond to incidents.

JWT access tokens (self-contained)
A JWT is signed and can be validated locally by your API. It can include claims (user id, roles, scopes, expiry).

Pros:

  • Fast validation (no database/network call)

  • Works well for high throughput

  • Good for distributed systems

Cons:

  • Harder to revoke immediately (until expiry)

  • If you put too much data in it, it becomes a “leaky database”

  • If compromised, it’s valid until it expires

Best for:

  • Short-lived access tokens (5–15 minutes)

  • Systems where “revocation by expiry” is acceptable for access tokens

  • Service-to-service calls with strict expiry

Reference (opaque) access tokens (server-side)
An opaque token is just a random string. Your API can’t understand it without calling the authorization server (introspection) or a shared token store.

Pros:

  • Easy immediate revocation

  • Central control (disable user/session instantly)

  • Less data exposure (token reveals nothing)

Cons:

  • Adds a lookup (latency + dependency)

  • Needs caching and high availability

  • Can become a bottleneck if designed poorly

Best for:

  • High security environments

  • Long-lived sessions where instant revocation matters

  • When you want central session management

A practical production pattern that works

Many strong systems combine both:

  • JWT access tokens: very short-lived (5–10 minutes)

  • Refresh tokens: longer-lived (days/weeks) and revocable

  • Optional: a revocation list or token rotation policies for higher security

Refresh tokens (why they exist)

Access tokens should be short-lived. Refresh tokens exist so users don’t have to log in every 10 minutes.

How it works:

  1. User logs in via Authorization Code + PKCE

  2. Client gets an access token (short) + refresh token (long)

  3. When access token expires, client uses refresh token to get a new access token

  4. Refresh token can be revoked (logout, suspicious activity, password change)

Important security rule:
Refresh tokens should be protected more than access tokens.

  • Store them securely (mobile secure storage; web: HttpOnly cookies if possible)

  • Use rotation (every refresh returns a new refresh token and invalidates the old one)

  • Detect reuse (if an old refresh token is used again, assume theft)

What “production auth” really means (the checklist)

If you want to think like a production backend engineer, validate these:

  • Token lifetimes: access short, refresh controlled

  • Revocation plan: how do you log out everywhere instantly?

  • RBAC/Scopes: how do you represent permissions (and keep them stable)?

  • Key rotation: can you rotate signing keys without downtime?

  • Audience/issuer checks: does your API reject tokens not meant for it?

  • Observability: do you log auth failures with trace context (without leaking secrets)?

  • Rate limiting: do you protect login/refresh endpoints?

Common mistakes (and quick fixes)

Mistake: Using ID tokens to call APIs
Fix: Use access tokens for APIs; ID tokens only for client identity.

Mistake: Long-lived JWT access tokens
Fix: Make access tokens short-lived; rely on refresh tokens.

Mistake: Putting sensitive data in JWT claims
Fix: Keep claims minimal; fetch sensitive info server-side when needed.

Mistake: No plan for revocation
Fix: Use short expiries + refresh token rotation and revocation.

What’s next in the series

Part 3 is GraphQL in production: schema design, preventing N+1, caching, and error handling—so your API stays fast and predictable under load.

The truly Limitless design subscription.

Say goodbye to expensive freelancers, and hello to limitless, lightning fast design.