Every access token has a built-in self-destruct timer. That is not a flaw — it is the design. Short-lived tokens limit the damage when one leaks, and refresh tokens let legitimate users stay signed in without re-entering credentials. If you are building anything that issues or consumes tokens, the expiration strategy is one of the most consequential decisions you will make. This guide walks through it.
Why access tokens expire at all
A token that never expires is a password that never changes. If it leaks — through a log file, a misconfigured proxy, a stolen laptop — whoever holds it has access forever, or until someone notices and revokes it manually. Expiration bounds that risk automatically: even if a token escapes, it stops working on its own.
Expiration also simplifies revocation. Maintaining a live revocation list for millions of tokens is expensive infrastructure. With short lifetimes, the system can afford to check revocation rarely, or not at all for the access token itself — the damage window is small by construction, and the refresh token (which is checked more carefully) carries the real trust.
Choosing a token lifetime
There is no universal correct lifetime. The right answer depends on what the token protects and how painful re-authentication is:
| Scenario | Common range teams use | Reasoning |
|---|---|---|
| User-facing web session | 15 minutes – 1 hour | Limits damage from stolen session tokens; refresh keeps UX smooth |
| Mobile app session | 30 minutes – a few hours | Re-login on mobile is painful, so lifetimes skew longer with rotation |
| Server-to-server (client credentials) | Minutes – 24 hours | Machines re-authenticate cheaply; short is easy here |
| High-risk operations (payments, admin) | Minutes | Sensitive actions deserve fresh authentication |
| CI/CD and automation tokens | Hours – days | Jobs run unattended; pair with tight scopes instead |
The pattern: shorter is safer, longer is friendlier. Refresh tokens exist precisely to resolve that tension — the access token stays short while the session stays long.
Refresh tokens: the companion credential
A refresh token is a longer-lived credential whose only job is to mint new access tokens. When the access token expires, the client sends the refresh token to the token endpoint and receives a fresh pair — no password prompt, no browser redirect. The OAuth flow that issues these tokens starts with the client application's credentials — see our guide on OAuth client IDs explained.
Because a refresh token is powerful, it gets stricter handling than the access token:
- Stored securely — never in localStorage for browser apps; httpOnly cookies or secure native storage instead.
- Rotated on use — each refresh issues a brand-new refresh token and invalidates the old one, so a stolen refresh token is detectable.
- Bound to the client where possible — tied to the device or client registration that created it.
- Revocable centrally — the authorization server keeps a record, so logout and "sign out everywhere" actually work.
Rotation and reuse detection
Rotation is the practice of issuing a new refresh token every time the old one is used. It turns theft into a visible event: if an attacker uses a stolen refresh token, the legitimate client's next refresh attempt fails because its token was already consumed. The server sees the same refresh token used twice — reuse detection — and can treat it as a compromise: revoke the whole token family and force re-authentication.
This is why refresh tokens are single-use in well-designed systems. The slight complexity of handling rotation races (a client retrying with an old token after a network hiccup) is worth it: it converts silent theft into an alarm.
Revocation: killing tokens early
Sometimes waiting for expiry is not enough — a user logs out, an employee leaves, a device is lost. Options:
- Revoke the refresh token — the cheapest effective move. The access token may live a few more minutes, but no new ones can be minted.
- Denylist (blocklist) — keep a list of revoked access tokens checked on each request. Accurate, but adds a lookup to the hot path; usually reserved for high-risk systems.
- Shorten lifetimes globally — a blunt instrument, but during an active incident, cutting access-token lifetime to minutes shrinks every attacker's window.
Sliding vs absolute sessions
| Sliding expiration | Absolute expiration | |
|---|---|---|
| How it works | Each activity extends the session | Session ends at a fixed time, no matter what |
| User experience | Active users never get logged out | Everyone re-authenticates on schedule |
| Security | A stolen session can persist while "active" | Guaranteed upper bound on session length |
| Best for | Productivity apps, dashboards | Banking, admin consoles, shared devices |
Many systems combine both: a sliding window for convenience capped by an absolute maximum session length.
Testing expiry without waiting
Expiry logic is notoriously hard to test against real providers — you cannot ask an identity provider to issue a token that dies in five seconds just for your test suite. This is where mock token generation earns its keep: generate tokens shaped like your real ones, with the expiry timestamps you choose, and verify that your middleware rejects expired tokens, refreshes gracefully, and handles clock skew the way you expect. Test the unhappy paths — expired, malformed, wrong audience — before production does it for you.
Key takeaway: keep access tokens short, refresh tokens rotated and revocable, and always have a logout story. Expiration is not an inconvenience to work around — it is the safety net the whole system rests on.
FAQ
What happens when an access token expires mid-request?
Nothing dramatic: the request in flight usually completes, and the next request gets a 401. A well-built client catches the 401, refreshes silently, and retries once — the user never notices.
Should refresh tokens expire?
Yes — with a much longer lifetime than access tokens, often days to weeks, plus an absolute maximum session length. "Never-expiring" refresh tokens turn one theft into permanent access.
Can I just use long-lived access tokens and skip refresh tokens?
You can, and some internal systems do. But you lose the security benefits: every token becomes a long-lived secret, rotation gets harder, and revocation becomes your only control. For anything user-facing, the short-access/long-refresh split is the standard for a reason.
How do I handle token refresh in a single-page app?
Keep the refresh token in an httpOnly cookie (set by your backend) rather than JavaScript-accessible storage, and let a backend endpoint perform the refresh. This keeps the powerful credential out of reach of cross-site scripting.
What is clock skew and why does it matter?
Servers disagree about the current time by seconds. Token validation usually allows a small leeway (often around a minute) so a token is not rejected just because the API server's clock runs slightly behind the issuer's.