In short
Short-lived access tokens are safer, but nobody wants to log in every 15 minutes. Refresh tokens quietly get you a fresh access token in the background so the user never notices.
In an auth-system context: the mechanism that silently issues new short-lived access tokens using a longer-lived refresh token, keeping users logged in without re-entering credentials.

Think of it like this
Mall parking with a monthly pass
Each entry, you get a paper slip valid for a few hours. Your monthly pass stays in the glovebox and is shown only at the office to get new slips. Lose a slip, little harm. The pass is guarded much more carefully.
Watch it happen
Silent refresh in the background
GET /orders (old token)
1/7The access token expired a minute ago, but the browser doesn't know yet.
The big ideas
What you need to know
Two tokens, two jobs
The access token is short-lived and sent on every API call. The refresh token lives longer and is used only to get new access tokens.
Keep the refresh token in an HttpOnly cookie
Scope it to the refresh endpoint path so it's sent only there, never readable by JavaScript.
Rotate on every use
Each refresh returns a new refresh token and kills the old one. If an old one is reused, someone stole it.
Logout means revoke
Delete the refresh token on the server at logout, not just in the browser.
See it in code
Walk through the code
1let refreshing: Promise<Response> | null = null;2 3export async function api(url: string, init: RequestInit = {}) {4 const opts = { ...init, credentials: "include" as const };5 let res = await fetch(url, opts);6 7 if (res.status === 401) {8 // share one refresh call if many requests fail at once9 refreshing ??= fetch("/auth/refresh", { method: "POST", credentials: "include" });10 const r = await refreshing;11 refreshing = null;12 if (!r.ok) { window.location.href = "/login"; return res; }13 res = await fetch(url, opts); // retry once14 }15 return res;16}1/4One shared promise, so many failing requests trigger only one refresh call.
Both tokens live in HttpOnly cookies here, so the browser attaches them for you. The shared promise stops five failed requests from firing five refresh calls.
Save yourself the pain
Common mistakes
Oops
Keeping the refresh token in localStorage next to the access token.
Do this instead
A refresh token is basically a long-term login. Keep it in an HttpOnly, Secure, SameSite cookie.
Oops
Making refresh tokens valid forever and never rotating them.
Do this instead
Give them an expiry, rotate on every use, and revoke them on logout or password change.
Your mission
Survive an expired token
Quick check
Your server sees a refresh token that was already used and rotated. What's the safest reaction?
Go deeper
- 1Diagram your app's full token lifecycle: issue, use, expire, refresh, revoke.
- 2Implement automatic refresh via an interceptor that retries failed requests after refreshing.
- 3Learn how refresh token rotation helps limit damage if a token is stolen.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.