Implementing JWT refresh tokens in a Node.js and Express application provides a more secure and scalable authentication flow. Below is a step-by-step guide to enhance your existing authentication system with JWT refresh tokens.
Step 1: Set Up Your Project
- Make sure you have your existing Node.js and Express application with user authentication using JWTs.
Step 2: Install Necessary Packages
Install the jsonwebtoken
and uuid
packages to handle JWT operations and generate refresh token identifiers.
npm install jsonwebtoken uuid
Step 3: Modify User Model
Add a field for storing refresh tokens in your user model. For example, in your models/user.js
:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
// Your existing fields
// New field for storing refresh tokens
refreshTokens: [{ type: String }],
});
const User = mongoose.model('User', userSchema);
module.exports = User;
Step 4: Generate Refresh Token on Login
When a user logs in and receives a new JWT access token, also generate and store a refresh token in the user's document. Modify your authentication route (routes/auth.js
or similar) to include refresh token generation:
const express = require('express');
const jwt = require('jsonwebtoken');
const uuid = require('uuid');
const User = require('../models/user');
const router = express.Router();
router.post('/login', async (req, res) => {
// Your existing login logic
// Generate a refresh token
const refreshToken = uuid.v4();
user.refreshTokens.push(refreshToken);
await user.save();
// Generate and send the new access token and refresh token to the client
const accessToken = generateAccessToken(user);
res.json({ accessToken, refreshToken });
});
function generateAccessToken(user) {
// Your existing access token generation logic
}
module.exports = router;
Step 5: Implement Refresh Token Route
Create a new route for handling token refresh. This route will receive a refresh token, verify it, and send back a new access token:
const express = require('express');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
const router = express.Router();
router.post('/refresh-token', async (req, res) => {
const { refreshToken } = req.body;
// Verify the refresh token
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, async (err, user) => {
if (err) {
return res.sendStatus(403);
}
// Check if the refresh token is still valid for this user
const dbUser = await User.findById(user._id);
if (!dbUser || !dbUser.refreshTokens.includes(refreshToken)) {
return res.sendStatus(403);
}
// Generate a new access token
const accessToken = generateAccessToken(dbUser);
res.json({ accessToken });
});
});
function generateAccessToken(user) {
// Your existing access token generation logic
}
module.exports = router;
Step 6: Handle Token Refresh on the Client
In your client-side code, implement a mechanism to refresh the access token using the refresh token. When the current access token expires, send a request to the /refresh-token
endpoint with the stored refresh token:
async function refreshAccessToken(refreshToken) {
try {
const response = await fetch('/auth/refresh-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ refreshToken }),
});
if (!response.ok) {
throw new Error('Failed to refresh access token');
}
const { accessToken } = await response.json();
// Use the new access token in your application
return accessToken;
} catch (error) {
console.error('Error refreshing access token:', error);
// Handle error or redirect to login
}
}
Step 7: Test the Implementation
Start your Express server.
node server.js
Test the login endpoint to get an access token and refresh token.
When the access token expires, use the refresh token to get a new access token by calling the
/refresh-token
endpoint.Handle token refresh errors appropriately, such as redirecting the user to the login page.
Congratulations! You've successfully implemented JWT refresh tokens in your Node.js and Express authentication flow. This enhances security by reducing the exposure of long-lived access tokens and provides a way to revoke access on the server side. Customize and expand this implementation based on your specific requirements.