jsonwebtoken is the industry-standard implementation of JSON Web Tokens for Node.js. It provides methods to sign, verify, and decode JWTs.
Installation
npm install jsonwebtoken
Signing a Token
Signing is the process of creating a new JWT. You take a payload (data) and a secret key, and the library generates a signed string.
const jwt = require('jsonwebtoken');
const payload = { userId: 123, role: 'admin' };
const secretKey = process.env.JWT_SECRET;
// Basic signing
const token = jwt.sign(payload, secretKey);
// With expiration (always recommended)
const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });
// Other expiration formats: '2 days', '10h', '7d', or seconds (e.g., 60)
Verifying a Token
Verifying checks if the token is authentic (not tampered with) and not expired. If invalid, it throws an error.
const receivedToken = '...';
try {
const decoded = jwt.verify(receivedToken, secretKey);
console.log("Token is valid:", decoded);
// Output: { userId: 123, role: 'admin', iat: 1700000000, exp: 1700003600 }
} catch(err) {
console.log("Invalid token:", err.message);
}
Handling Specific Errors
jwt.verify(token, secretKey, (err, decoded) => {
if (err) {
if (err.name === 'TokenExpiredError') {
console.log("Token has expired");
} else if (err.name === 'JsonWebTokenError') {
console.log("Token is malformed/invalid");
}
} else {
console.log("Success!", decoded);
}
});
Decoding (No Verification)
Read the token’s content without validating the signature. Never trust this for authentication.
const decoded = jwt.decode(token);
console.log(decoded);
// Returns payload without verifying signature
Express Middleware
Protect routes by checking the Authorization header for a valid token.
function authenticateToken(req, res, next) {
// 1. Get token from header: "Bearer <token>"
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401); // Unauthorized
// 2. Verify token
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403); // Forbidden
// 3. Attach user to request
req.user = user;
next();
});
}
// Usage
app.get('/dashboard', authenticateToken, (req, res) => {
res.json({ message: `Welcome ${req.user.username}` });
});
Complete Login Example
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
async function login(req, res) {
// 1. Find user
const user = await User.findOne({ email: req.body.email });
if (!user) throw new Error('User not found');
// 2. Verify password
const valid = await bcrypt.compare(req.body.password, user.password);
if (!valid) throw new Error('Invalid password');
// 3. Generate token
const token = jwt.sign(
{ user_id: user._id, email: user.email, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
// 4. Send via secure cookie
res.cookie('token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000
});
res.json({ message: 'Login successful' });
}
Asymmetric Algorithms (RS256)
For microservices or higher security, use asymmetric encryption:
- Private Key: Signs (creates) the token
- Public Key: Verifies the token
const fs = require('fs');
// Signing with private key
const privateKey = fs.readFileSync('private.key');
const token = jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256' });
// Verifying with public key
const publicKey = fs.readFileSync('public.pem');
jwt.verify(token, publicKey, { algorithms: ['RS256'] }, (err, decoded) => {
console.log(decoded.foo); // 'bar'
});
Token Refresh Pattern
For security, use two tokens:
- Access Token: Short life (15 mins). Used for API calls.
- Refresh Token: Long life (7 days). Stored in DB. Used only to get a new access token.
// At login: Generate both tokens
const accessToken = jwt.sign(user, JWT_SECRET, { expiresIn: '15m' });
const refreshToken = jwt.sign({ user_id: user.id }, REFRESH_SECRET, { expiresIn: '7d' });
// Store refresh token in DB for revocation
await RefreshToken.create({ user_id: user.id, token: refreshToken });
// Refresh endpoint
async function refresh(req, res) {
const { refreshToken } = req.body;
const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
// Check if token exists in DB (not revoked)
const stored = await RefreshToken.findOne({ token: refreshToken });
if (!stored) throw new Error('Token revoked');
// Issue new access token
const user = await User.findById(decoded.user_id);
const newAccessToken = jwt.sign(
{ user_id: user.id, email: user.email },
JWT_SECRET,
{ expiresIn: '15m' }
);
res.json({ accessToken: newAccessToken });
}
Implementing Logout
JWT is stateless, but you can implement effective logout with a blacklist:
async function logout(req, res) {
await TokenBlacklist.create({
token: req.cookies.token,
expires_at: new Date(req.user.exp * 1000)
});
res.clearCookie('token');
res.json({ message: 'Logged out' });
}
// Verify checks blacklist
async function verifyToken(token) {
const decoded = jwt.verify(token, secret);
const blacklisted = await TokenBlacklist.findOne({ token });
if (blacklisted) throw new Error('Token revoked');
return decoded;
}
Environment Variables
JWT_SECRET=your_super_secret_key_minimum_32_characters
JWT_EXPIRY=7d
REFRESH_TOKEN_SECRET=different_secret_for_refresh_tokens
COOKIE_NAME=authToken
Debugging
Use jwt.io to paste a token and visually inspect its header and payload structure.