Demystifying the Tokens
Every time you hit "Sign in with Google" or log into an app, a handful of tokens fly between systems. Here's what each one does — no fluff.
---
## Authorization Code — The Handshake
This is the shortest-lived token in the pipeline. It's not a token you keep — it's a one-time handshake that proves the user just authorized your app.
```
Browser → Authorization Server → gets a code
Browser → Your App → passes the code
Your App → Authorization Server → exchanges the code for real tokens
```
- **Lifetime:** Seconds to minutes
- **Format:** Opaque string (a random blob)
- **What it does:** Proves the user authorized your app. Exchange it once, then it's dead.
- **Why it exists:** So the browser never sees the actual tokens. The code goes through the frontend, but the real tokens come back over a secure backend channel.
---
## Access Token — The Key Card
This is the token your app sends to APIs. Think of it like a hotel key card — it opens specific doors for a limited time.
```http
GET /api/orders HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
```
- **Lifetime:** Minutes to hours (typically 15 min — 1 hour)
- **Format:** JWT or opaque string
- **What it does:** Proves to the API that your app has permission to access specific data
- **Who reads it:** The API (resource server). Your app just passes it along.
- **Contents:** Scopes, permissions, maybe a user ID — but never PII
**Key rule:** The access token tells the API *what you can do*, not *who you are*. If you need identity info, use the ID token.
---
## ID Token — The Photo ID
This one only shows up in OpenID Connect flows. It's proof of identity — a JWT signed by the identity provider that says "this user just logged in."
```json
{
"iss": "https://auth.example.com/",
"sub": "248289761001",
"name": "Jane Doe",
"email": "jane@example.com",
"exp": 1704070800
}
```
- **Lifetime:** Minutes to hours
- **Format:** Always a JWT (so your app can verify the signature)
- **What it does:** Tells your app who the user is — name, email, avatar, etc.
- **Who reads it:** Your app (frontend). Never send this to an API.
- **Validation:** Your app must verify the signature using the provider's JWKS keys
**ID token vs. access token:** The ID token is for *your app* to know the user. The access token is for *APIs* to authorize requests. They serve completely different jobs.
---
## Refresh Token — The Re-Entry Pass
Access tokens expire quickly by design. The refresh token lets your app get new access tokens without bothering the user to log in again.
```
Your App → Authorization Server → POST /token with refresh_token
Authorization Server → Your App → new access token (+ maybe new refresh token)
```
- **Lifetime:** Days to months (long-lived)
- **Format:** Opaque string
- **What it does:** Gets you new access tokens silently in the background
- **Storage:** Must be stored securely (server-side, httpOnly cookie, or secure storage)
- **Rotation:** Best practice is to issue a new refresh token each time one is used (so old ones can't be replayed)
**When to use it:** After the user logs in the first time, all subsequent visits should use the refresh token to get fresh access tokens. The user never sees a login screen again until the refresh token itself expires or is revoked.
---
## The Big Picture
Here's how they flow together in a standard OIDC Authorization Code flow with PKCE:
```
User clicks "Sign In"
↓
Browser → Authorization Server → User authenticates
↓
Browser gets an Authorization Code (lives for seconds)
↓
Your backend exchanges the code for:
├── ID Token → proves who the user is
├── Access Token → lets you call APIs
└── Refresh Token → gets you new access tokens later
↓
Access token expires → use refresh token → new access token
↓
Refresh token expires → user needs to re-authenticate
```
## One-Liner Summary
| Token | Purpose | Lifetime | Read by |
|-------|---------|----------|---------|
| Authorization Code | One-time handshake | Seconds | Auth server only |
| Access Token | Calls APIs | Minutes–hours | The API |
| ID Token | Identifies the user | Minutes–hours | Your app |
| Refresh Token | Gets new tokens | Days–months | Auth server only |