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/scopesSelf-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
/refreshendpoint.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
tokenVersionper 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:
localStorageis 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(orLax) 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
SameSitecookie behavior).Strong answer: "httpOnly cookies for the refresh token, paired with SameSite and CSRF protections, is the safer default for browser apps."

Join the conversation! Your thoughts help the community grow.