Table of Contents
- Introduction to Refresh Tokens
- Setting Up the Project
- JWT Authentication and Refresh Token Flow
- Creating the Routes for Access Token and Refresh Token
- Securing the Application
- Conclusion
1. Introduction to Refresh Tokens
When using JWTs for authentication, access tokens are typically short-lived (e.g., 1 hour) for security reasons. However, you don’t want users to have to log in again every hour. This is where refresh tokens come in. A refresh token allows the user to obtain a new access token without needing to log in again.
The flow involves:
- A short-lived access token used for making authenticated requests.
- A long-lived refresh token used to obtain a new access token when the current one expires.
When the access token expires, the client sends the refresh token to the server to get a new access token.
2. Setting Up the Project
Install Dependencies
If you haven’t already, start by installing the required dependencies:
npm install express jsonwebtoken bcryptjs dotenv
Environment Configuration (.env
)
Make sure you have the following keys in your .env
file:
JWT_SECRET=your_jwt_secret
JWT_EXPIRATION=3600 # Access Token Expiration in seconds (1 hour)
JWT_REFRESH_SECRET=your_refresh_jwt_secret
JWT_REFRESH_EXPIRATION=86400 # Refresh Token Expiration in seconds (24 hours)
3. JWT Authentication and Refresh Token Flow
The flow works as follows:
- Login: After successful login, the server generates two tokens:
- An access token (short-lived).
- A refresh token (long-lived).
- Access Token Expiration: When the access token expires, the client uses the refresh token to request a new access token.
- Refresh Token Rotation: For security, each time a refresh token is used to obtain a new access token, a new refresh token is generated.
4. Creating the Routes for Access Token and Refresh Token
Step 1: Set up JWT Authentication (Generating Tokens)
Here’s the code to generate both the access token and refresh token:
server.js
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const dotenv = require('dotenv');
const app = express();
dotenv.config();
app.use(express.json());
// Dummy users (replace with a real database in production)
let users = [
{ id: 1, username: 'user1', password: 'password123', role: 'user' },
];
// Generate Access Token
function generateAccessToken(user) {
return jwt.sign(
{ id: user.id, username: user.username, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
}
// Generate Refresh Token
function generateRefreshToken(user) {
return jwt.sign(
{ id: user.id, username: user.username, role: user.role },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '24h' }
);
}
// Login Route - Authenticates user and returns access & refresh tokens
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username);
if (!user) return res.status(400).send('Invalid credentials');
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) return res.status(400).send('Invalid credentials');
// Generate Access Token & Refresh Token
const accessToken = generateAccessToken(user);
const refreshToken = generateRefreshToken(user);
res.json({ accessToken, refreshToken });
});
// Route to Refresh Access Token using Refresh Token
app.post('/token', (req, res) => {
const refreshToken = req.body.refreshToken;
if (!refreshToken) return res.status(401).send('Refresh Token Required');
// Validate the refresh token
jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET, (err, user) => {
if (err) return res.status(403).send('Invalid Refresh Token');
// Generate a new access token
const accessToken = generateAccessToken(user);
const newRefreshToken = generateRefreshToken(user); // Rotate refresh token
res.json({ accessToken, refreshToken: newRefreshToken });
});
});
// Secure route example
app.get('/protected', (req, res) => {
const token = req.headers['authorization']?.split(' ')[1]; // Bearer token
if (!token) return res.status(401).send('Access Token Required');
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).send('Invalid or Expired Token');
res.json({ message: 'Welcome to protected route!', user });
});
});
app.listen(3000, () => console.log('Server running on http://localhost:3000'));
Step 2: Explanation
- Login Route (
/login
):- Accepts user credentials.
- If credentials are valid, it generates both an access token (1 hour expiration) and a refresh token (24 hours expiration).
- Sends both tokens back to the client.
- Token Route (
/token
):- This is used when the client wants to refresh their access token.
- The client sends the refresh token to the server.
- If the refresh token is valid, the server generates and returns a new access token and refresh token.
- Protected Route (
/protected
):- A protected route that requires a valid access token.
- If the access token is valid, the server responds with the protected content.
5. Securing the Application
Refresh Token Storage
- Client-side: The refresh token should be stored securely on the client side. You can store it in HttpOnly cookies (more secure than
localStorage
orsessionStorage
), which ensures that the token is not accessible via JavaScript but can still be sent with requests automatically.
Token Expiration
- Access Tokens: Access tokens should have a relatively short expiration time to minimize risk in case they are compromised. A typical expiration time is 1 hour.
- Refresh Tokens: Refresh tokens are more long-lived (24 hours or more) and should be rotated regularly.
Rotation & Blacklisting
- When a refresh token is used to obtain a new access token, it’s recommended to rotate the refresh token — generate a new refresh token every time the client refreshes their access token.
- Implementing a blacklist for refresh tokens (storing invalidated refresh tokens) can help secure your application.
Implement HTTPS
Always run your application over HTTPS in production to protect sensitive data during transmission, including tokens.
6. Conclusion
In this guide, we implemented refresh tokens alongside JWT for maintaining a secure session and preventing frequent logins.
Summary:
- Access Token: Short-lived token used for making API requests.
- Refresh Token: Long-lived token used to obtain a new access token when the previous one expires.
- Token Rotation: Each time a refresh token is used, a new refresh token is generated for security.
- Storage: Always store refresh tokens securely, preferably in HttpOnly cookies.
- Protection: Make sure your app is served over HTTPS to secure the token exchange.