Refresh Tokens & Session Management
Refresh Tokens & Session Management
Last updated: 3/7/2025
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 Type | Purpose | Expiry | Where Stored? |
---|---|---|---|
Access Token | Grants short-term API access | 15 mins - 1 hour | Authorization Header |
Refresh Token | Generates a new access token | Days - Weeks | HTTP-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. π