Authentication and JWT
Implementing authentication with JWT tokens
Challenge
Section titled “Challenge”Securely authenticate users and maintain sessions using JWT tokens.
Explanation
Section titled “Explanation”JWT (JSON Web Tokens) provide a stateless way to authenticate requests without server-side sessions.
JWT Basics
Section titled “JWT Basics”const jwt = require('jsonwebtoken');
const secret = 'your-secret-key';
// Create tokenconst token = jwt.sign( { userId: 123, email: 'user@example.com' }, secret, { expiresIn: '24h' });
console.log(token);// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
// Verify tokentry { const decoded = jwt.verify(token, secret); console.log('Valid token:', decoded);} catch (error) { console.error('Invalid token:', error.message);}Express Authentication Middleware
Section titled “Express Authentication Middleware”const express = require('express');const app = express();
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, secret, (error, user) => { if (error) { return res.status(403).json({ error: 'Invalid token' }); }
req.user = user; next(); });}
// Protected routeapp.get('/protected', authenticateToken, (req, res) => { res.json({ message: 'Secret data', user: req.user });});
// Login routeapp.post('/login', (req, res) => { // Verify username/password const user = { userId: 123, email: 'user@example.com' };
const token = jwt.sign(user, secret, { expiresIn: '24h' });
res.json({ token });});Refresh Tokens
Section titled “Refresh Tokens”// Access token (short-lived) + Refresh token (long-lived)
let refreshTokens = [];
app.post('/login', (req, res) => { const user = { userId: 123, email: 'user@example.com' };
const accessToken = jwt.sign(user, secret, { expiresIn: '15m' }); const refreshToken = jwt.sign(user, secret, { expiresIn: '7d' });
refreshTokens.push(refreshToken);
res.json({ accessToken, refreshToken });});
app.post('/refresh', (req, res) => { const { refreshToken } = req.body;
if (!refreshTokens.includes(refreshToken)) { return res.status(401).json({ error: 'Invalid refresh token' }); }
jwt.verify(refreshToken, secret, (error, user) => { if (error) { return res.status(403).json({ error: 'Refresh token expired' }); }
const newAccessToken = jwt.sign( { userId: user.userId, email: user.email }, secret, { expiresIn: '15m' } );
res.json({ accessToken: newAccessToken }); });});
app.post('/logout', (req, res) => { const { refreshToken } = req.body; refreshTokens = refreshTokens.filter(t => t !== refreshToken); res.json({ message: 'Logged out' });});OAuth 2.0 Integration
Section titled “OAuth 2.0 Integration”const passport = require('passport');const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({ clientID: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, callbackURL: '/auth/google/callback'}, (accessToken, refreshToken, profile, done) => { // Find or create user User.findOrCreate({ googleId: profile.id }, (error, user) => { return done(error, user); });}));
app.get('/auth/google', passport.authenticate('google', { scope: ['email'] }));
app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), (req, res) => { const token = jwt.sign( { userId: req.user.id }, secret, { expiresIn: '24h' } ); res.json({ token }); });Password Hashing
Section titled “Password Hashing”const bcrypt = require('bcryptjs');
// Registerapp.post('/register', async (req, res) => { const { email, password } = req.body;
// Hash password const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash(password, salt);
const user = await User.create({ email, password: hashedPassword });
res.json({ user });});
// Loginapp.post('/login', async (req, res) => { const { email, password } = req.body;
const user = await User.findOne({ email }); if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); }
// Compare password const isValid = await bcrypt.compare(password, user.password); if (!isValid) { return res.status(401).json({ error: 'Invalid credentials' }); }
const token = jwt.sign( { userId: user.id, email: user.email }, secret, { expiresIn: '24h' } );
res.json({ token });});Best Practices
Section titled “Best Practices”// 1. Use HTTPS always// 2. Store tokens securely// 3. Short-lived access tokensconst token = jwt.sign(data, secret, { expiresIn: '15m' });
// 4. Use strong secretsconst secret = require('crypto') .randomBytes(32) .toString('hex');
// 5. Validate claimsjwt.verify(token, secret, (error, user) => { if (error) throw error;
if (user.exp * 1000 < Date.now()) { throw new Error('Token expired'); }});
// 6. Handle token revocationconst tokenBlacklist = new Set();
app.post('/logout', (req, res) => { const { token } = req.body; tokenBlacklist.add(token); res.json({ message: 'Logged out' });});
function authenticateToken(req, res, next) { const token = req.headers['authorization']?.split(' ')[1];
if (tokenBlacklist.has(token)) { return res.status(401).json({ error: 'Token revoked' }); }
jwt.verify(token, secret, (error, user) => { if (error) return res.status(403).json({ error: 'Invalid' }); req.user = user; next(); });}