Refresh Tokens & Session Management

Refresh Tokens & Session Management

Last updated: 3/7/2025

1 hour
Medium

Refresh Tokens & Session Management

🌍 Introduction

In the previous lessons, we implemented authentication using JWT and OAuth. However, access tokens expire for security reasons, requiring users to log in again.
To improve the user experience, we can implement refresh tokens to generate new access tokens without requiring a full login.

βœ… Why Use Refresh Tokens?

βœ” Short-lived access tokens prevent long-term exposure if stolen.
βœ” Refresh tokens allow users to stay logged in without re-entering credentials.
βœ” Reduces the risk of unauthorized access by expiring access tokens quickly.

In this lesson, we’ll:

  • Understand how refresh tokens work.
  • Implement a refresh token system in an Express.js API.
  • Securely store and revoke refresh tokens.

πŸ“Œ 1. Understanding Access Tokens vs. Refresh Tokens

Token TypePurposeExpiryWhere Stored?
Access TokenGrants short-term API access15 mins - 1 hourAuthorization Header
Refresh TokenGenerates a new access tokenDays - WeeksHTTP-only Cookies or Database

βœ… How Refresh Tokens Work

1️⃣ User logs in β†’ Receives Access Token (short-lived) & Refresh Token (long-lived).
2️⃣ When the Access Token expires, the client sends the Refresh Token to request a new Access Token.
3️⃣ If the Refresh Token is valid, a new Access Token is issued.
4️⃣ If the Refresh Token is revoked or expired, the user must log in again.


πŸ“Œ 2. Setting Up Refresh Tokens in an Express.js API

βœ… Step 1: Install Dependencies

npm install express jsonwebtoken dotenv cookie-parser

βœ” jsonwebtoken β†’ Handles JWT creation and verification.
βœ” cookie-parser β†’ Parses HTTP-only cookies for storing refresh tokens.


βœ… Step 2: Modify the Login Route to Issue Refresh Tokens

Modify routes/auth.js:

const express = require("express"); const jwt = require("jsonwebtoken"); const bcrypt = require("bcryptjs"); const User = require("../models/User"); const router = express.Router(); // Generate Access Token const generateAccessToken = (user) => { return jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: "15m" }); }; // Generate Refresh Token const generateRefreshToken = (user) => { return jwt.sign({ userId: user._id }, process.env.REFRESH_SECRET, { expiresIn: "7d" }); }; // Login Route with Refresh Token router.post("/login", async (req, res) => { try { const { email, password } = req.body; const user = await User.findOne({ email }); if (!user || !(await bcrypt.compare(password, user.password))) { return res.status(400).json({ error: "Invalid credentials" }); } const accessToken = generateAccessToken(user); const refreshToken = generateRefreshToken(user); // Store refresh token in HTTP-only cookie res.cookie("refreshToken", refreshToken, { httpOnly: true, secure: true, sameSite: "Strict", }); res.json({ accessToken }); } catch (error) { res.status(500).json({ error: "Server error" }); } }); module.exports = router;

βœ… Access tokens expire in 15 minutes.
βœ… Refresh tokens last 7 days and are stored securely in cookies.


βœ… Step 3: Creating a Refresh Token Endpoint

Add a new /refresh-token route to issue a new access token:

router.post("/refresh-token", (req, res) => { const refreshToken = req.cookies.refreshToken; if (!refreshToken) return res.status(401).json({ error: "No refresh token provided" }); try { const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET); const newAccessToken = generateAccessToken({ userId: decoded.userId }); res.json({ accessToken: newAccessToken }); } catch (error) { res.status(403).json({ error: "Invalid or expired refresh token" }); } });

βœ… Validates the refresh token and issues a new access token.


βœ… Step 4: Revoking Refresh Tokens (Logout)

To log out a user, we delete the refresh token from cookies:

router.post("/logout", (req, res) => { res.clearCookie("refreshToken"); res.json({ message: "Logged out successfully" }); });

βœ… Ensures the refresh token is revoked on logout.


πŸ“Œ 3. Testing the Refresh Token System

βœ… 1️⃣ Log In to Receive Tokens

POST /auth/login
{ "email": "alice@example.com", "password": "mypassword" }

Response:

{ "accessToken": "eyJhbGciOiJIUzI1NiIs..." }

βœ… Refresh token is stored in an HTTP-only cookie.


βœ… 2️⃣ Access a Protected Route with Access Token

GET /profile Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

βœ” If valid, access is granted.
❌ If expired, request a new access token.


βœ… 3️⃣ Request a New Access Token Using the Refresh Token

POST /auth/refresh-token

βœ” If valid, a new access token is issued.
❌ If expired, the user must log in again.


βœ… 4️⃣ Logout and Revoke Refresh Token

POST /auth/logout

βœ” Deletes refresh token from cookies.
βœ” Requires the user to log in again.


πŸ“Œ 4. Securing Refresh Tokens

βœ… Store Refresh Tokens Securely

βœ” Use HTTP-only cookies instead of localStorage to prevent XSS attacks.
βœ” Set refresh tokens to expire (e.g., 7 days).
βœ” Clear refresh tokens on logout to prevent reuse.


🎯 Summary

βœ… Refresh tokens allow users to stay logged in without re-entering credentials.
βœ… Access tokens expire quickly, reducing security risks.
βœ… Refresh tokens are stored securely in HTTP-only cookies.
βœ… Revoking refresh tokens ensures user sessions are properly managed.


βœ… Next Lesson: Closing Thoughts on Authentication & Authorization

We’ll wrap up the Authentication & Authorization chapter with key takeaways and next steps. πŸš€