If you've worked with login systems, you've used JWTs. But a surprising number of engineers — even senior ones — can use access and refresh tokens correctly in code without being able to clearly explain why the system is designed the way it is. That gap is exactly where interviewers like to dig in.
This post explains both tokens in plain language, then walks through the tricky interview questions that actually separate "I copy-pasted this from a tutorial" from "I understand the security model."
The Simple Version
Think of logging into an app like checking into a hotel.
Access Token = Your hotel room keycard. It lets you open your room door right now. It works for a short time (say, until checkout). If someone steals it, they can only use it for a little while before it stops working.
Refresh Token = Your reservation/ID at the front desk. You don't show this every time you open your door — that would be slow and risky to keep handing over. You only show it when your keycard stops working, so the front desk can issue you a new keycard. It's more powerful, more sensitive, and you protect it more carefully.
That's the entire mental model. Everything else is just engineering around that idea.
Access Token
Short-lived — commonly 5–15 minutes, sometimes up to an hour. This is a convention, not a rule set by any spec; teams tune it based on their own risk tolerance.
Sent with every API request (usually in the Authorization: Bearer <token> header)
Contains claims about the user: sub (user id), role, exp (expiry), sometimes permissions/scopes
Self-contained — the server can verify it instantly using a signature, without a database lookup
If it leaks, the damage is limited because it dies soon anyway
Refresh Token
Long-lived — commonly days to weeks, again by convention rather than a fixed rule
Used for one job only: getting a new access token when the old one expires
Usually stored more carefully (httpOnly cookie, secure storage on mobile, or a server-side session record)
Often tracked in a database so it can be revoked or rotated
If it leaks, the damage is much bigger — so it needs stronger protection
Technical note: This post uses "JWT" loosely to mean "the tokens your auth flow issues." Strictly speaking, only the access token is usually an actual JWT. The refresh token doesn't need to be self-describing — its only job is to be looked up server-side — so a lot of real systems issue it as a plain opaque random string stored in a database, not a JWT at all. This is, in fact, the default in Duende IdentityServer / ASP.NET Core, where refresh tokens are typically opaque reference tokens rather than JWTs. Both approaches (JWT refresh token or opaque refresh token) are valid; just don't assume a refresh token has to be a JWT.
The Flow, Step by Step
User logs in with email/password.
Server verifies credentials and issues two tokens: an access token and a refresh token.
Client stores both and sends the access token with every API call.
Server checks the access token's signature and expiry on each request — no database call needed.
Eventually the access token expires. The next API call gets a 401 Unauthorized.
Client sends the refresh token to a dedicated /refresh endpoint.
Server checks the refresh token is valid and not revoked, then issues a new access token (and often a new refresh token too — more on that below).
If the refresh token is also expired, invalid, or revoked, the user is sent back to the login screen.
That's it. Two tokens, two lifespans, two jobs.
Why Bother With Two Tokens At All?
This is usually the first interview question, and it's worth having a crisp answer:
A single long-lived token would be convenient but dangerous — if it leaks, it's valid for a long time and (because JWTs are stateless) hard to revoke. A single short-lived token would be secure but annoying — users would get logged out every few minutes. Splitting into two tokens lets you get both: a short-lived token that limits the blast radius of a leak, and a long-lived token that's used rarely enough that it can be stored more securely and tracked/revoked server-side.
This "blast radius vs. convenience" framing is the core idea interviewers are checking for.
Tricky Interview Questions (and How to Answer Them)
These are the questions that go beyond "define a JWT" and actually test whether you understand the trade-offs.
1. "JWTs are stateless — so how do you log a user out or revoke a token before it expires?"
Why it's tricky: People often say "just delete it from local storage" — which only logs the user out of that browser tab, not the system.
How to answer: Acknowledge the contradiction directly — a truly stateless JWT can't be revoked without some server-side check, which reintroduces state. Then give real mechanisms:
Keep access token lifetimes very short, so revocation matters less.
Maintain a server-side denylist (e.g., in Redis) of revoked token IDs, checked on each request.
Use token versioning: store a tokenVersion per user in the database; increment it on logout/password change; embed the version in the JWT and compare it on each request.
Track refresh tokens in a database so they can always be deleted/revoked — this is usually the real "logout."
2. "Where should the refresh token be stored on the client — localStorage or an httpOnly cookie?"
Why it's tricky: It sounds like a simple storage question but it's really a question about XSS vs CSRF trade-offs.
How to answer:
localStorage is readable by any JavaScript running on the page — so if there's an XSS vulnerability anywhere in the app, the attacker can simply read the token straight out.
An httpOnly, Secure, SameSite=Strict (or Lax) cookie can't be read by JavaScript at all, which neutralizes XSS token theft.
The trade-off is CSRF: cookies are sent automatically by the browser, so you need CSRF protection (e.g., a CSRF token, or relying on SameSite cookie behavior).
Strong answer: "httpOnly cookies for the refresh token, paired with SameSite and CSRF protections, is the safer default for browser apps."
3. "What is refresh token rotation, and why does it matter?"
Why it's tricky: Many candidates know the term but can't explain the security mechanism behind it.
How to answer: Every time a refresh token is used, the server issues a brand-new refresh token and immediately invalidates the old one. The real power is reuse detection: if the old (already-used) refresh token ever shows up again, that's a strong signal it was stolen and used by someone else in parallel — so the server can revoke the entire token family and force a re-login. It turns refresh tokens from a static secret into a tripwire.
4. "If a refresh token gets stolen, what's the actual impact, and how do you limit it?"
Why it's tricky: Tests whether you think about attacker capability, not just the happy path.
How to answer: An attacker with a stolen refresh token can mint new access tokens indefinitely — effectively impersonating the user until the token expires or is revoked. Mitigations:
Rotation + reuse detection (above).
Bind the refresh token to a device fingerprint, IP range, or user-agent, and reject refresh attempts that don't match.
Keep refresh token lifetime as short as the UX can tolerate.
Let users see and revoke active sessions/devices.
5. "JWTs are 'signed, not encrypted' — what does that actually mean, and why does it matter?"
Why it's tricky: People confuse signing with encryption constantly, and it leads to real security bugs (e.g., putting sensitive data in a JWT payload).
How to answer: The payload is just base64url-encoded JSON — anyone can decode and read it (try it on jwt.io). The signature only guarantees the content hasn't been tampered with; it does nothing to hide the content. So: never put passwords, SSNs, or other sensitive data directly in the payload. If confidentiality is genuinely needed, you'd use JWE (encrypted tokens), which is rare in practice — the usual fix is just "don't put secrets in the payload."
6. "HS256 vs RS256 — when would you pick one over the other?"
Why it's tricky: Tests whether you understand signing algorithms beyond "I picked the default in the library."
How to answer:
HS256 (symmetric): one shared secret signs and verifies. Simple, fast, fine for a monolith. The downside: every service that needs to verify tokens also needs the secret — and anything that can verify can also forge, which is risky as the secret spreads across services.
RS256 (asymmetric): a private key signs (held only by the auth server), and a public key verifies (can be freely distributed to any number of microservices). Safer in a distributed/microservices architecture because verifying services never hold anything that could be used to forge a token.
7. "How do you handle token refresh on the client without breaking the user's experience — especially with multiple API calls firing at once?"
Why it's tricky: This is a real production bug magnet. Naively, five simultaneous expired requests trigger five simultaneous refresh calls, which can race and invalidate each other (especially with rotation).
How to answer: Use an interceptor (e.g., in Axios/fetch) that catches 401 responses. The first failure triggers a refresh call; any other requests that fail while that refresh is in-flight get queued, not sent again — they wait for the single in-flight refresh to resolve, then retry with the new token. This avoids the "refresh stampede" problem.
8. "What's the difference between what the access token is for and what authentication actually means here?"
Why it's tricky: It probes whether you conflate authentication and authorization.
How to answer: Authentication ("who are you") happens once, at login, with credentials. The access token doesn't re-authenticate the user on every request — it's a proof that authentication already happened, plus authorization data (roles/scopes) the server uses to decide what the user can do. The refresh token's job is purely to extend the authenticated session without forcing the user to log in again.
9. "Why validate the aud and iss claims — isn't checking the signature enough?"
Why it's tricky: A great question for catching people who've only used JWTs at a surface level.
How to answer: A valid signature only proves the token was issued by a trusted authority — it doesn't prove the token was meant for this service. In a multi-service or multi-tenant system, a perfectly valid token issued for Service A could be replayed against Service B if Service B doesn't check it. Validating aud (intended audience) and iss (issuer) prevents this kind of token replay/confused-deputy scenario.
Quick Reference Table
| Access Token | Refresh Token |
|---|
| Lifespan | Minutes | Days to weeks |
| Sent with | Every API request | Only to the /refresh endpoint |
| Can be revoked instantly? | Hard (stateless) | Yes (usually tracked server-side) |
| Storage sensitivity | Lower (short-lived) | Higher (long-lived, more damage if leaked) |
| Typical client storage | Memory or short-lived storage | httpOnly secure cookie (browser) |
| Main risk if leaked | Limited window of misuse | Persistent impersonation until revoked |
The One-Line Summary to Remember
Access tokens are short-lived and used constantly; refresh tokens are long-lived and used rarely — and that asymmetry is the entire point of the design. Every interview question above is really just asking you to defend a different angle of that one sentence.