Stop 10 · Authentication & AuthorizationTopics · 7/11

Refresh Tokens

Part of the Backend Developer Roadmap

In short

Short access tokens are safer, but nobody wants to log in every 15 minutes. A refresh token quietly fetches a fresh access token in the background, so users stay logged in for days.

A longer-lived token used to silently obtain a new short-lived access token, keeping users logged in without forcing frequent re-authentication or leaving long-lived tokens exposed.

The blob character shows a season pass and gets a fresh wristband at a ticket desk.

Think of it like this

Season pass and daily wristband

At an amusement park, your season pass stays safe in your wallet. Each morning you show it at the desk and get a fresh wristband for the rides. Lose the wristband and it's useless by evening. The access token is the wristband; the refresh token is the season pass.

Watch it happen

Silent refresh with rotation

App
API

GET /orders (access)

1/6The app calls the API with its 15-minute access token.

The big ideas

What you need to know

Two tokens, two lifetimes

Access token: minutes, sent on every call. Refresh token: days or weeks, sent only to the refresh endpoint.

Track refresh tokens

Keep a hashed copy in the database, so you can revoke them on logout or password change.

Rotate on every use

Each refresh returns a new refresh token and retires the old one. Reuse of an old one signals theft.

Keep them away from scripts

On the web, put the refresh token in an HttpOnly, Secure cookie scoped to the refresh path.

See it in code

Walk through the code

ts
1app.post("/auth/refresh", async (req, res) => {2  const presented = req.cookies.refresh_token;3  const record = await db.refreshTokens.findByHash(sha256(presented ?? ""));4 5  if (!record || record.expiresAt < new Date()) return res.sendStatus(401);6  if (record.usedAt) {7    await db.refreshTokens.revokeFamily(record.familyId);8    return res.sendStatus(401);9  }10 11  await db.refreshTokens.markUsed(record.id);12  const { accessToken, refreshToken } = await issuePair(record.userId, record.familyId);13  res.cookie("refresh_token", refreshToken, { httpOnly: true, secure: true, sameSite: "strict", path: "/auth/refresh" });14  res.json({ accessToken });15});

1/4Read the refresh token from its cookie and find its hashed copy in the database.

req.cookies needs cookie-parser. sha256, issuePair and db are your own helpers; issuePair saves the new refresh token's hash.

Save yourself the pain

Common mistakes

Oops

Making access tokens last a week so you can skip refresh tokens.

Do this instead

A leaked week-long token can't be cancelled. Keep access tokens short and let refresh tokens handle long logins.

Oops

Storing refresh tokens in plain text in the database.

Do this instead

Store a hash, just like a password. A database leak then doesn't hand out working tokens.

Your mission

Build silent refresh

~45 min

Quick check

Why rotate the refresh token on every use?

Go deeper

  1. 1Implement a refresh-token endpoint that issues a new access token.
  2. 2Learn why access tokens are kept short-lived and refresh tokens longer-lived.
  3. 3Implement refresh token rotation to limit damage if one 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