JAVASCRIPT
Secure Strategy for API Token (JWT) Storage and Management
Implement a secure strategy for managing JSON Web Tokens (JWTs), distinguishing between short-lived access tokens and HttpOnly refresh tokens to enhance authentication security.
const express = require('express');
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');
const app = express();
app.use(express.json());
app.use(cookieParser());
const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET || 'your_access_secret'; // Use a strong secret from env
const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET || 'your_refresh_secret'; // Use a strong secret from env
// --- Mock User & Refresh Token Store ---
// In a real app, this would be a database
const users = [{
id: 1,
username: 'testuser',
password: 'password123' // Hashed in real app
}];
let refreshTokens = []; // Store revoked tokens or valid ones with associated user IDs
// --- Token Generation ---
function generateAccessToken(user) {
return jwt.sign({ id: user.id, username: user.username }, ACCESS_TOKEN_SECRET, { expiresIn: '15m' }); // Short-lived
}
function generateRefreshToken(user) {
const refreshToken = jwt.sign({ id: user.id, username: user.username }, REFRESH_TOKEN_SECRET, { expiresIn: '7d' }); // Longer-lived
refreshTokens.push(refreshToken); // Store the refresh token (or its hash) in DB/store for validation/revocation
return refreshToken;
}
// --- Authentication & Token Issuance ---
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password); // Replace with secure password verification
if (!user) return res.status(401).json({ message: 'Invalid credentials.' });
const accessToken = generateAccessToken(user);
const refreshToken = generateRefreshToken(user);
// Store refresh token securely in an HttpOnly cookie
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production', // Use secure cookie in production
sameSite: 'strict', // Protect against CSRF
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
});
// Send access token to client (e.g., in response body for JS to store in memory or http header)
res.json({ accessToken: accessToken, message: 'Logged in successfully.' });
});
// --- Token Refresh Endpoint ---
app.post('/api/token', (req, res) => {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) return res.status(401).json({ message: 'Refresh token not found.' });
if (!refreshTokens.includes(refreshToken)) return res.status(403).json({ message: 'Refresh token invalid or revoked.' }); // Check against stored tokens
jwt.verify(refreshToken, REFRESH_TOKEN_SECRET, (err, user) => {
if (err) return res.status(403).json({ message: 'Refresh token invalid or expired.' });
const newAccessToken = generateAccessToken(user);
res.json({ accessToken: newAccessToken });
});
});
// --- Logout (Token Revocation) ---
app.post('/api/logout', (req, res) => {
const refreshToken = req.cookies.refreshToken;
refreshTokens = refreshTokens.filter(token => token !== refreshToken); // Remove from store (or blacklist it)
res.clearCookie('refreshToken');
res.status(204).send();
});
// --- Protected Route Example ---
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) return res.status(401).json({ message: 'Access token required.' });
jwt.verify(token, ACCESS_TOKEN_SECRET, (err, user) => {
if (err) return res.status(403).json({ message: 'Access token invalid or expired.' });
req.user = user;
next();
});
}
app.get('/api/protected', authenticateToken, (req, res) => {
res.json({ message: `Welcome, ${req.user.username}! This is protected data.` });
});
const PORT = process.env.PORT || 3005;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
How it works: This snippet outlines a secure strategy for managing API tokens, specifically JSON Web Tokens (JWTs), by separating them into short-lived access tokens and longer-lived refresh tokens. Access tokens, often stored in client-side memory or an HTTP header, are used for authenticating requests to protected resources. Refresh tokens, stored securely in HttpOnly, SameSite, and Secure cookies, are used to obtain new access tokens when the current one expires. This approach mitigates common JWT vulnerabilities: HttpOnly cookies prevent client-side JavaScript access (reducing XSS impact), short-lived access tokens minimize exposure if compromised, and storing refresh tokens allows for server-side revocation during logout or security incidents.