Introduction to Refresh Tokens

In previous lessons, we explored JWT fundamentals, common vulnerabilities, and defenses like token blacklisting and short-lived token expiration. While these improve security, they can negatively impact user experience by requiring frequent re-authentication.

Refresh tokens address this by allowing clients to obtain new access tokens without re-authentication. They work alongside access tokens:

  • Access tokens: Short-lived, used for API access
  • Refresh tokens: Long-lived, used to get new access tokens

This separation follows the principle of least privilege. Token rotation enhances security by generating a new refresh token each time one is used, preventing replay attacks. Let's implement this system to address identified vulnerabilities.

Implementing Refresh Token Functionality

To implement refresh tokens, we need to modify our authentication system to generate, store, and validate both access and refresh tokens. Let's start by examining how we can create and manage these tokens.

Note: In most applications, users don’t manually request a new access token. Instead, the frontend (UI) automatically handles it in the background. This is done using refresh tokens whenever an access token expires.

First, we'll need to generate both tokens during login. We'll add a unique jti (JWT ID) claim to each refresh token to help us track and manage them securely. We generate this jti using Math.random().toString(36).substring(2), which creates a random string for each token:

Why use the jti claim in refresh tokens?
The jti (JWT ID) claim provides a unique identifier for each refresh token. By assigning a unique jti to every token, the server can track and manage individual tokens—allowing it to invalidate a specific token after use (token rotation) or in case of compromise. This is essential for security because it prevents replay attacks: if a refresh token is stolen, it cannot be reused once its jti has been invalidated. Using jti enables fine-grained control over token validity and enforces single-use refresh tokens during rotation.

Now, let's see how these tokens are used during login:

In this code, we're generating two tokens: a short-lived access token (15 minutes) and a longer-lived refresh token (7 days) with a unique jti claim. Both are stored as HTTP-only cookies to protect against XSS attacks. The access token is used for regular API access, while the refresh token will be used to obtain new access tokens when the current one expires.

Next, we need to create a /refresh endpoint that allows clients to exchange a valid refresh token for a new access token:

This endpoint extracts the refresh token from cookies, verifies it, and if valid, issues a new access token. The client can call this endpoint whenever the access token expires, allowing for continuous authentication without requiring the user to log in again.

However, this implementation still has a security issue: if a refresh token is stolen, it can be used repeatedly until it expires (7 days in our example). This is where token rotation comes in.

Implementing Token Rotation

Token rotation adds an important security layer by ensuring that refresh tokens are single-use. Each time a refresh token is used, it's invalidated and a new one is issued. This prevents an attacker who has stolen a refresh token from using it after the legitimate user has already used it.

To implement token rotation, we need to:

  1. Track all issued refresh tokens (using their jti)
  2. Invalidate a refresh token after it's used
  3. Issue a new refresh token with each refresh request

Let's break down the implementation step by step.

1. Storing Refresh Tokens

First, we need a way to keep track of issued refresh tokens. We'll use the jti claim as a unique identifier for each refresh token. For demonstration, we'll use an in-memory store (a Map). In production, use a persistent database.

2. Storing Refresh Tokens on Login

When a user logs in, generate a refresh token with a unique jti and store the jti:

3. Validating and Rotating Refresh Tokens

When a refresh token is used, we need to:

  • Check if its jti exists in our store
  • Remove (invalidate) it
  • Issue a new refresh token and store its jti
Summary

Refresh tokens solve the user experience problem of frequent re-authentication when using short-lived access tokens. This lesson introduces the dual-token system where short-lived access tokens handle API access while long-lived refresh tokens obtain new access tokens without requiring users to log in again. We implemented refresh token functionality in our authentication system, including secure token generation, storage, and validation. We then enhanced security through token rotation, making refresh tokens single-use by invalidating them after use and issuing new ones, tracked by their unique jti claim. The jti is generated using Math.random().toString(36).substring(2) to ensure each token is unique and unpredictable, which is critical for preventing replay attacks and enforcing single-use tokens.

In the upcoming practice, you'll apply these concepts by implementing a secure token rotation system to protect your application. 🔄🔐

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal