Authentication is where a lot of apps quietly accumulate risk. Tokenized sessions — done right — give you stateless scaling and strong security. Done wrong, they hand attackers a long-lived key. Here's the mental model we apply on every build.
What's the difference between an access token and a refresh token?
An access token is short-lived (minutes) and proves who you are on each request. A refresh token is longer-lived and does one job: get a new access token. Keeping them separate means a leaked access token expires fast, while the refresh token can be guarded more tightly. Most implementations we build use JWTs (RFC 7519) for the access token — signed, self-contained, and verifiable without a database round-trip — while the refresh token is an opaque, database-backed value.
Why should you rotate refresh tokens?
Every time a refresh token is used, issue a new one and invalidate the old. If an old token is ever replayed, that's a signal of theft — revoke the whole chain. Rotation turns a stolen refresh token from a permanent backdoor into a one-shot that trips an alarm. A minimal rotation flow looks like this: 1) client sends refresh token → 2) server checks it against the stored, unexpired record → 3) server issues a new access + refresh token pair → 4) server marks the old refresh token record as used → 5) if a "used" token is ever presented again, revoke every token issued to that user and force re-authentication.
Where should you store tokens?
Prefer httpOnly, secure cookies for refresh tokens so JavaScript can't read them, which neutralises most XSS-based theft. Keep access tokens in memory rather than localStorage. Pair cookies with CSRF protection (SameSite=Strict or a synchronizer token), consistent with the OWASP Session Management Cheat Sheet.
Common pitfalls
- Long-lived access tokens "for convenience" — defeats the whole model.
- No revocation path, so a compromised account can't be cut off.
- Putting sensitive data in the token payload (it's only base64, not encrypted).
- Skipping
aud/issclaim validation, so a token issued for one service is silently accepted by another.
The payoff
Get this right and you have sessions that scale horizontally, expire gracefully, and can be revoked on demand — the foundation every secure app should stand on.
Security-first is our default, not an add-on. We build auth, RBAC, and encryption in from day one.