Script Valley
Authentication From Scratch
JWT AuthenticationLesson 3.4

Access tokens and refresh tokens explained

access token lifetime, refresh token lifetime, token rotation, refresh endpoint, silent refresh, token family tracking, refresh token reuse detection

Two-Token Strategy

A single long-lived JWT is a liability โ€” if it is stolen, the attacker has access until it expires. The solution is two tokens with different lifetimes and purposes.

Access token โ€” short-lived (15โ€“60 minutes), sent with every API request, verified quickly without a database lookup.

Refresh token โ€” long-lived (7โ€“30 days), stored more carefully, sent only to a single /auth/refresh endpoint to obtain a new access token.

Refresh Endpoint

app.post('/auth/refresh', async (req, res) => {
  const refreshToken = req.cookies.refreshToken;
  if (!refreshToken) return res.status(401).end();

  try {
    const payload = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
    // Optionally: check token exists in DB and has not been revoked

    const newAccessToken = jwt.sign(
      { sub: payload.sub },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );
    res.json({ accessToken: newAccessToken });
  } catch {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
});

Refresh Token Rotation

On each refresh, issue a new refresh token and invalidate the old one. If an old refresh token is ever used again, it signals theft โ€” immediately invalidate all tokens for that user. This is called refresh token reuse detection.

Up next

How to invalidate JWTs before they expire

Sign in to track progress

Access tokens and refresh tokens explained โ€” JWT Authentication โ€” Authentication From Scratch โ€” Script Valley โ€” Script Valley