Stop 12 · AuthenticationTopics · 10/10

Refresh Tokens

Part of the Frontend Developer Roadmap

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.

The jaadu shows a monthly parking pass at a mall office window and receives a fresh paper slip.

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

Browser
API
/auth/refresh

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

ts
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

~45 min

Quick check

Your server sees a refresh token that was already used and rotated. What's the safest reaction?

Go deeper

  1. 1Diagram your app's full token lifecycle: issue, use, expire, refresh, revoke.
  2. 2Implement automatic refresh via an interceptor that retries failed requests after refreshing.
  3. 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.

Download
InsideEdge