What is JWT?
JWT (JSON Web Token) is a compact, URL-safe means of representing claims to be transferred between two parties.
JWT Structure
javascript
// Header
{
"alg": "HS256",
"typ": "JWT"
}
// Payload
{
"userId": "123",
"email": "user@example.com",
"iat": 1516239022,
"exp": 1516242622
}
// Signature
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)Implementation
Setup
bash
npm install jsonwebtoken bcrypt
npm install @types/jsonwebtoken --save-devUser Model
javascript
import mongoose from 'mongoose';
import bcrypt from 'bcrypt';
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
},
password: {
type: String,
required: true,
minlength: 8,
},
name: {
type: String,
required: true,
},
});
// Hash password before saving
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
// Method to compare passwords
userSchema.methods.comparePassword = async function(password) {
return bcrypt.compare(password, this.password);
};
export default mongoose.model('User', userSchema);Token Generation
javascript
import jwt from 'jsonwebtoken';
const generateToken = (user) => {
const payload = {
userId: user._id,
email: user.email,
};
const accessToken = jwt.sign(
payload,
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
payload,
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
};Registration
javascript
export const register = async (req, res) => {
try {
const { email, password, name } = req.body;
// Check if user exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ error: 'User already exists' });
}
// Create user
const user = await User.create({
email,
password,
name,
});
// Generate tokens
const { accessToken, refreshToken } = generateToken(user);
// Save refresh token
user.refreshTokens.push(refreshToken);
await user.save();
res.status(201).json({
user: {
id: user._id,
email: user.email,
name: user.name,
},
accessToken,
refreshToken,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
};Login
javascript
export const login = async (req, res) => {
try {
const { email, password } = req.body;
// Find user
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Check password
const isValid = await user.comparePassword(password);
if (!isValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate tokens
const { accessToken, refreshToken } = generateToken(user);
// Save refresh token
user.refreshTokens.push(refreshToken);
await user.save();
res.json({
user: {
id: user._id,
email: user.email,
name: user.name,
},
accessToken,
refreshToken,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
};Middleware
javascript
export const auth = async (req, res, next) => {
try {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Authentication required' });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.userId);
if (!user) {
return res.status(401).json({ error: 'User not found' });
}
req.user = user;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};Refresh Token
javascript
export const refreshToken = async (req, res) => {
try {
const { refreshToken } = req.body;
const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
const user = await User.findById(decoded.userId);
if (!user || !user.refreshTokens.includes(refreshToken)) {
return res.status(401).json({ error: 'Invalid refresh token' });
}
// Generate new tokens
const { accessToken, refreshToken: newRefreshToken } = generateToken(user);
// Remove old refresh token
user.refreshTokens = user.refreshTokens.filter(t => t !== refreshToken);
user.refreshTokens.push(newRefreshToken);
await user.save();
res.json({ accessToken, refreshToken: newRefreshToken });
} catch (error) {
res.status(401).json({ error: 'Invalid refresh token' });
}
};Logout
javascript
export const logout = async (req, res) => {
try {
const { refreshToken } = req.body;
const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
const user = await User.findById(decoded.userId);
if (user) {
user.refreshTokens = user.refreshTokens.filter(t => t !== refreshToken);
await user.save();
}
res.json({ message: 'Logged out successfully' });
} catch (error) {
res.status(500).json({ error: error.message });
}
};Routes
javascript
import express from 'express';
import { register, login, refreshToken, logout } from '../controllers/auth';
import { auth } from '../middleware/auth';
const router = express.Router();
router.post('/register', register);
router.post('/login', login);
router.post('/refresh-token', refreshToken);
router.post('/logout', logout);
router.get('/me', auth, (req, res) => {
res.json({ user: req.user });
});
export default router;Frontend Integration
React Context
javascript
import { createContext, useContext, useState, useEffect } from 'react';
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check if user is logged in
const token = localStorage.getItem('accessToken');
if (token) {
// Verify token and get user
verifyToken(token);
}
setLoading(false);
}, []);
const login = async (email, password) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
localStorage.setItem('accessToken', data.accessToken);
localStorage.setItem('refreshToken', data.refreshToken);
setUser(data.user);
};
const logout = async () => {
await fetch('/api/auth/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`,
},
body: JSON.stringify({
refreshToken: localStorage.getItem('refreshToken'),
}),
});
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout, loading }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);Security Best Practices
- 1Strong secrets: Use environment variables
- 2Short access token lifetime: 15 minutes
- 3Long refresh token lifetime: 7 days
- 4HTTPS only: Never transmit over HTTP
- 5HttpOnly cookies: Store tokens securely
- 6Rotate refresh tokens: Generate new on refresh
- 7Revoke tokens: On logout/password change
Conclusion
JWT authentication is secure when implemented correctly. Follow these practices for production-ready authentication.