โ† All flows

Refresh Token Flow

For getting a new access token without logging in again

A refresh token is a long-lived credential the app receives along with its access token. When the access token expires, the app sends the refresh token to the token endpoint (the login server's address for issuing tokens) and gets a new access token.

The result: the user does not have to sign in again.

See it step by step

๐Ÿ–ฅ๏ธ
App
๐Ÿ”
Login server
๐Ÿ“ฆ
API
Expired token

Step 1 of 4

The token has expired

The app asks the API for your data, but the API says the access token has expired.

When to use this flow

  • Long-lived sessions โ€” Keep users signed in for days or weeks
  • Background sync โ€” Apps that call an API while the user is away
  • Client Credentials โ€” That flow has no user, so just request a new token

Good practices

  • Store refresh tokens securely โ€” They last a long time. Keep them on the server, or in secure storage on a device
  • Save the new refresh token โ€” Many servers rotate them, so the old one stops working after one use
  • Request only what you need โ€” A refreshed token can only carry scopes the user already approved
  • Refresh only when needed โ€” Wait for expiry (or a 401) rather than refreshing on every request

Common mistakes

  • Putting refresh tokens in local storage โ€” Any script on the page could steal them
  • Sending a refresh token to the API โ€” It belongs only at the token endpoint
  • Ignoring errors โ€” If refresh fails with invalid_grant, send the user through login again
  • Logging tokens โ€” Never write them to logs or URLs
Code example
Refresh Token example
javascript
// Refresh an expired access token
async function refreshAccessToken(refreshToken) {
  const res = await fetch('https://auth.provider.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET, // server-side apps only
    }),
  });
  if (!res.ok) throw new Error('Refresh failed, ask the user to log in again');
  return res.json(); // { access_token, expires_in, refresh_token? }
}