The OAuth Mental Model
Four roles (memorize these)
OAuth names four actors. Everything else hangs off them.
flowchart TB
RO["Resource Owner<br/>(the user)"]
AS["Authorization Server<br/>(Keycloak, Okta, Google, Auth0…)"]
RS["Resource Server<br/>(API)"]
CL["Client<br/>(your app)"]
RO -->|"grants consent / trust"| AS
RO -->|"owns resources"| RS
AS -->|"issues access token"| RS
CL -->|"presents access token"| RS
CL -->|"requests authorization"| AS
| Role | Plain English | Example |
|---|---|---|
| Resource owner | User who can grant access | Alice |
| Client | App requesting access | Your Spring Boot app |
| Authorization server | Issues tokens after consent | Keycloak realm |
| Resource server | API that accepts tokens | Your /api/orders service |
One company often runs both authorization server and resource server (Google issues tokens and hosts Gmail API). In microservices, they are usually separate.
Three token types you will meet
Access token
- Purpose: Prove authorization to call an API
- Audience: Resource server
- Format: Often opaque or JWT
- Lifetime: Short (minutes to an hour)
GET /api/orders HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Refresh token
- Purpose: Obtain a new access token without re-prompting the user
- Audience: Authorization server only
- Lifetime: Longer (days/weeks), revocable
- Storage: Server-side or secure httpOnly cookie — never
localStoragein a browser
ID token (OIDC, not pure OAuth)
- Purpose: Identity claims about the authenticated user
- Audience: Client application
- Format: JWT
- Lifetime: Short; not an API credential
Rule of thumb: Use the access token for APIs. Use the ID token for login/session identity in your client.
Scopes: the valet key notches
Scopes limit what a token can do. They are strings agreed between client and authorization server.
openid profile email
read:orders write:orders
| Scope | Meaning |
|---|---|
openid |
OIDC request — return an ID token |
profile |
Name, picture, etc. |
email |
Email address |
read:orders |
Custom API scope (you define in Keycloak) |
The authorization server shows the user a consent screen listing scopes. The issued access token embeds (or maps to) only approved scopes.
Flows = how the client gets tokens
A grant type (flow) is the protocol path from "user wants to use app" to "client holds tokens."
| Flow | Use today? | Notes |
|---|---|---|
| Authorization Code + PKCE | Yes — default | SPAs, mobile, server apps |
| Client credentials | Yes | Machine-to-machine, no user |
| Device code | Yes | TVs, CLI devices |
| Implicit | No | Replaced by Auth Code + PKCE |
| Resource owner password | No | Deprecated; bypasses consent |
A later post in this series walks through Authorization Code + PKCE in detail.
OIDC sits on top of OAuth
flowchart LR
O["OAuth 2.0"]
OIDC["OpenID Connect"]
O -->|"authorization"| Q1["Can this app access that API?"]
OIDC -->|"identity layer"| Q2["Who is the user?"]
OIDC -.->|"built on"| O
OIDC adds:
id_tokenJWT/userinfoendpoint- Discovery document at
/.well-known/openid-configuration
When people say "OAuth login," they almost always mean OIDC.
Sessions vs tokens (where confusion starts)
| Concept | Where it lives | Typical use |
|---|---|---|
| OAuth tokens | Client + auth server contract | API access |
| App session | Your server or cookie | "Logged in to our app" |
A common pattern:
- User completes OIDC flow → your backend receives tokens
- Backend validates ID token, creates app session (cookie)
- Backend stores refresh token server-side
- Browser only sees session cookie; never holds refresh token
Your app still needs session management. OAuth does not replace it.
Go deeper
JWT access tokens vs opaque tokens
JWT (self-contained):
- Resource server can validate locally (signature + claims)
- Harder to revoke instantly
- Must keep claims small
Opaque:
- Resource server introspects with auth server
- Revocation is immediate
- Extra network hop
Keycloak can do both. Choose based on revocation needs and API topology.
Audience (aud) and issuer (iss)
JWTs carry iss (who minted) and aud (who may consume). Validating only the signature without checking aud is a common vulnerability — your API must reject tokens minted for a different client.
Multi-tenant mental model
In Keycloak:
- Realm ≈ tenant
- Client ≈ your application registration
- Users live in the realm
Okta/Auth0 use different names (Authorization Server, Application) but the same idea: you register your app and get client_id + credentials.
What you should know after this post
- You can name all four roles and one example of each
- You know access vs refresh vs ID token
- You default to Authorization Code + PKCE for user-facing clients
- You separate "OAuth tokens" from "my app's session"