DEV Community

keshav Sandhu
keshav Sandhu

Posted on

Refresh token: What, Why, How. πŸš€

Refresh tokens allows you to issue short-lived access tokens and longer-lived refresh tokens. When the access token expires, the client can send the refresh token to get a new access token. This provides an additional layer of control for logout or token expiration.

  • Access Token: Short-lived (e.g., 15 minutes), used to access protected resources.
  • Refresh Token: Long-lived (e.g., 30 days), used to request new access tokens after expiration.

Refresh Token Flow:

  1. User Logs In:
    • The server sends both an access token (JWT) and a refresh token.
  2. User Logs Out:
    • The client removes both the access token and refresh token.
    • The server can invalidate the refresh token (e.g., remove it from the database).
  3. Access Token Expiry:
    • When the access token expires, the client can use the refresh token to obtain a new access token from the server.

This approach allows for more robust logout functionality, as refresh tokens can be invalidated server-side, ensuring that the user can't continue using old tokens.


2. Why Use Refresh Tokens?

Refresh tokens help you separate concerns of authentication (verifying a user's identity) and session management (keeping a user logged in over time) while maintaining security.

Key Benefits:

  • Short-Lived Access Tokens (Access Control):

    • If the access token is short-lived (e.g., 15 minutes), it limits the exposure time of an access token.
    • If an attacker steals the access token, the damage is limited by its short lifespan.
  • Long-Lived Refresh Tokens (Reauthentication):

    • Refresh tokens allow the user to stay logged in without needing to authenticate again after the access token expires.
    • Refresh tokens are kept securely (on the client side, usually in an httpOnly cookie), and only sent to the server when a new access token is needed.

3. How the Client Handles Refresh Tokens

Here's the flow for handling refresh tokens on the client side, assuming you're using JWTs.

Login Flow:

  1. User logs in with their credentials (email + password).
  2. The server responds with:

    • Access Token (short-lived, e.g., 15 minutes)
    • Refresh Token (long-lived, e.g., 30 days)
  3. The client stores:

    • The access token (e.g., in memory, localStorage, or a cookie for API calls).
    • The refresh token (preferably in an httpOnly cookie, which is less vulnerable to XSS attacks).

Requesting New Access Token with Refresh Token:

  • When the client makes a request to a protected route, it includes the access token in the Authorization header.
  • If the access token expires, the client will send the refresh token to the server to get a new access token:
    • The server verifies the refresh token.
    • If valid, the server issues a new access token and sends it back to the client.
    • The server may also refresh the refresh token itself, issuing a new one with each access token renewal (to prevent long-term use of an old refresh token).

Logout Flow:

  • To log out, the client can:
    • Remove both the access token and refresh token from the client (i.e., from localStorage or cookies).
    • Optionally, the client can notify the server to invalidate the refresh token (by blacklisting or removing it from the database).

4. How to Implement Refresh Tokens on the Client-Side

Here’s how you would manage the refresh token on the client side, using localStorage, sessionStorage, or httpOnly cookies.

1. Storing the Tokens

  • Access Token:

    • You may store it in localStorage or sessionStorage to make it easily available for API requests. However, this could expose the token to XSS attacks if the site is vulnerable.
    • Alternatively, you can store it in memory (in a JS variable) while the app is running (but this means the user will need to log in again if the page is refreshed).
  • Refresh Token:

    • httpOnly cookies are the most secure option for storing refresh tokens, as it prevents access to the refresh token via JavaScript (protecting from XSS attacks).
    • Example: Set-Cookie: refreshToken=<value>; HttpOnly; Secure; SameSite=Strict.

2. Making API Requests with the Access Token

For each request to a protected route, you send the access token in the Authorization header:

fetch('https://api.example.com/protected', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${accessToken}`, // Attach the access token
  },
})
.then(response => response.json())
.then(data => {
  // Handle the API response
})
.catch(error => {
  // Handle errors
});
Enter fullscreen mode Exit fullscreen mode

3. Handling Access Token Expiry and Refreshing

If the access token expires, you can request a new one by using the refresh token.

  • First, check if the access token has expired.
  • If expired, send a request to your backend to refresh the token.

Example refresh token request:

// Function to get a new access token using refresh token
function refreshAccessToken() {
  return fetch('https://api.example.com/refresh', {
    method: 'POST',
    body: JSON.stringify({ refreshToken }),
    headers: {
      'Content-Type': 'application/json',
    },
  })
  .then(response => response.json())
  .then(data => {
    const { accessToken, refreshToken: newRefreshToken } = data;
    // Store the new tokens (in memory, localStorage, etc.)
    storeTokens(accessToken, newRefreshToken);
  })
  .catch(error => {
    // Handle error, e.g., if refresh token is invalid or expired
  });
}
Enter fullscreen mode Exit fullscreen mode

4. Removing Tokens on Logout

To log out, you simply remove both the access token and refresh token from wherever they are stored:

// Remove from localStorage
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');

// Remove from cookies
document.cookie = 'refreshToken=; Max-Age=0; path=/;';
Enter fullscreen mode Exit fullscreen mode

5. Server-Side: Handling Refresh Tokens

When the client sends a refresh token to get a new access token, the server will:

  1. Verify the refresh token (check its signature, expiration, etc.).
  2. If valid:
    • Issue a new access token.
    • Optionally issue a new refresh token.
    • If the refresh token is expired or invalid, deny the request and require the user to log in again.

Example Express Route for Refreshing Tokens:

// src/routes/refreshTokenRoute.ts
import express from 'express';
import { verifyRefreshToken, generateAccessToken } from '../utils/jwtUtils';
import { getRefreshTokenFromCookies } from '../utils/cookieUtils';

const router = express.Router();

router.post('/refresh', async (req, res) => {
  const refreshToken = getRefreshTokenFromCookies(req);

  if (!refreshToken) {
    return res.status(401).json({ message: 'Refresh token missing' });
  }

  try {
    const user = await verifyRefreshToken(refreshToken); // Verify refresh token

    const newAccessToken = generateAccessToken(user.id); // Generate a new access token
    const newRefreshToken = generateRefreshToken(user.id); // Optionally, generate a new refresh token

    res.json({
      accessToken: newAccessToken,
      refreshToken: newRefreshToken, // Optionally send the new refresh token
    });
  } catch (error) {
    res.status(403).json({ message: 'Invalid refresh token' });
  }
});

export default router;
Enter fullscreen mode Exit fullscreen mode

Conclusion

Refresh tokens provide a more secure and flexible approach for managing long-lived user sessions in a stateless authentication system. They allow you to:

  • Keep access tokens short-lived for security (limiting exposure if the token is stolen).
  • Use refresh tokens to request new access tokens without requiring the user to log in repeatedly.
  • Ensure logout by simply removing both tokens from the client-side storage.

This separation between access tokens and refresh tokens gives you more control over security and user experience.

What are your thoughts😊?

Top comments (0)