JWT Authentication: Secure Your Web Apps
JSON Web Tokens (JWT) are the industry standard for secure authentication in modern web applications. Used by companies like Auth0, Firebase, and AWS Cognito, JWTs provide a stateless, scalable way to authenticate users.
What is JWT?
A JWT is a compact, URL-safe token that contains JSON data. It consists of three parts:
- Header - Algorithm and token type
- Payload - Claims (user data)
- Signature - Verification
Example JWT: ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ```
How JWT Works
- User logs in with credentials
- Server validates and creates JWT
- Server sends JWT to client
- Client stores JWT (localStorage/cookie)
- Client sends JWT with each request
- Server verifies JWT and processes request
Implementation
Backend (Node.js/Express)
```javascript const jwt = require(‘jsonwebtoken’); const bcrypt = require(‘bcrypt’);
// Register app.post(‘/register’, async (req, res) => { const { email, password } = req.body;
// Hash password const hashedPassword = await bcrypt.hash(password, 10);
// Save user to database const user = await db.user.create({ email, password: hashedPassword, });
res.json({ message: ‘User created’ }); });
// Login app.post(‘/login’, async (req, res) => { const { email, password } = req.body;
// Find user const user = await db.user.findOne({ where: { email } }); if (!user) { return res.status(401).json({ error: ‘Invalid credentials’ }); }
// Verify password const validPassword = await bcrypt.compare(password, user.password); if (!validPassword) { return res.status(401).json({ error: ‘Invalid credentials’ }); }
// Create JWT const token = jwt.sign( { userId: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: ‘7d’ } );
res.json({ token }); });
// Protected route app.get(‘/profile’, authenticateToken, async (req, res) => { const user = await db.user.findById(req.user.userId); res.json(user); });
// Middleware function authenticateToken(req, res, next) { const authHeader = req.headers[‘authorization’]; const token = authHeader && authHeader.split(’ ’)[1];
if (!token) { return res.status(401).json({ error: ‘No token provided’ }); }
jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err) { return res.status(403).json({ error: ‘Invalid token’ }); } req.user = user; next(); }); } ```
Frontend (React)
```javascript // Login async function login(email, password) { const response = await fetch(‘/api/login’, { method: ‘POST’, headers: { ‘Content-Type’: ‘application/json’ }, body: JSON.stringify({ email, password }), });
const { token } = await response.json();
// Store token localStorage.setItem(‘token’, token); }
// Make authenticated request async function fetchProfile() { const token = localStorage.getItem(‘token’);
const response = await fetch(‘/api/profile’, { headers: { ‘Authorization’: `Bearer ${token}`, }, });
return response.json(); }
// Axios interceptor axios.interceptors.request.use((config) => { const token = localStorage.getItem(‘token’); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); ```
Refresh Tokens
For better security, use refresh tokens:
```javascript app.post(‘/login’, async (req, res) => { // … validate user
const accessToken = jwt.sign( { userId: user.id }, process.env.JWT_SECRET, { expiresIn: ‘15m’ } // Short-lived );
const refreshToken = jwt.sign( { userId: user.id }, process.env.REFRESH_TOKEN_SECRET, { expiresIn: ‘7d’ } // Long-lived );
// Store refresh token in database await db.refreshToken.create({ token: refreshToken, userId: user.id, });
res.json({ accessToken, refreshToken }); });
app.post(‘/refresh’, async (req, res) => { const { refreshToken } = req.body;
// Verify refresh token const tokenRecord = await db.refreshToken.findOne({ where: { token: refreshToken }, });
if (!tokenRecord) { return res.status(403).json({ error: ‘Invalid refresh token’ }); }
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, user) => { if (err) { return res.status(403).json({ error: ‘Invalid refresh token’ }); }
// Create new access token
const accessToken = jwt.sign(
{ userId: user.userId },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
res.json({ accessToken });
}); }); ```
Best Practices
- Use HTTPS - Always
- Short expiration - 15-30 minutes for access tokens
- Secure storage - httpOnly cookies or secure storage
- Validate on every request - Don’t trust tokens
- Use strong secrets - 256-bit random strings
- Implement refresh tokens - For better UX
- Blacklist tokens - For logout
- Rate limit - Prevent brute force
Security Considerations
```javascript // Don’t store sensitive data in JWT const token = jwt.sign( { userId: user.id }, // ✅ OK // { password: user.password }, // ❌ Never! process.env.JWT_SECRET );
// Use httpOnly cookies (immune to XSS) res.cookie(‘token’, token, { httpOnly: true, secure: true, sameSite: ‘strict’, maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days });
// Implement logout (token blacklist) app.post(‘/logout’, authenticateToken, async (req, res) => { await db.tokenBlacklist.create({ token: req.token, expiresAt: req.user.exp, }); res.json({ message: ‘Logged out’ }); }); ```
Secure your apps with JWT!