JWT Authentication in Express
While session-based authentication works well for traditional web apps, modern APIs often use **JWT (JSON Web Tokens)** for authentication. JWTs are stateless, scalable, and perfect for single-page applications and mobile apps.
What is JWT?
JWT (pronounced "jot") is an open standard for securely transmitting information between parties as a JSON object. It's digitally signed, so it can be verified and trusted. A JWT consists of three parts:
- Header: Contains metadata about the token (type, signing algorithm).
- Payload: Contains the claims (user data, expiration, etc.).
- Signature: Verifies the token hasn't been tampered with.
Think of JWT as a digitally signed passport. The passport contains your information and is stamped by an authority. Anyone can read it, but only the authority can verify it's authentic.
JWT Authentication Flow
- User logs in with credentials.
- Server verifies credentials and generates a JWT.
- Server sends JWT to client (usually in response body or cookie).
- Client stores JWT (localStorage, sessionStorage, or cookie).
- Client includes JWT in Authorization header for subsequent requests.
- Server verifies JWT and grants access.
Installation
npm install jsonwebtoken bcryptComplete JWT Authentication Example
1. User Model (models/User.js)
const mongoose = require('mongoose');const bcrypt = require('bcrypt');
const userSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true }, email: { type: String, required: true, unique: true }, password: { type: String, required: true }, role: { type: String, enum: ['user', 'admin'], default: 'user' }}, { timestamps: 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(candidatePassword) { return await bcrypt.compare(candidatePassword, this.password);};
module.exports = mongoose.model('User', userSchema);2. JWT Utilities (utils/jwt.js)
const jwt = require('jsonwebtoken');
const generateToken = (user) => { return jwt.sign( { id: user._id, username: user.username, role: user.role }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN || '7d' } );};
const verifyToken = (token) => { try { return jwt.verify(token, process.env.JWT_SECRET); } catch (err) { return null; }};
module.exports = { generateToken, verifyToken };3. Auth Middleware (middleware/auth.js)
const { verifyToken } = require('../utils/jwt');
const authenticate = (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'No token provided' }); } const token = authHeader.split(' ')[1]; const decoded = verifyToken(token); if (!decoded) { return res.status(401).json({ error: 'Invalid or expired token' }); } req.user = decoded; next();};
const authorize = (...roles) => { return (req, res, next) => { if (!req.user) { return res.status(401).json({ error: 'Not authenticated' }); } if (!roles.includes(req.user.role)) { return res.status(403).json({ error: 'Insufficient permissions' }); } next(); };};
module.exports = { authenticate, authorize };4. Auth Routes (routes/auth.js)
const express = require('express');const User = require('../models/User');const { generateToken } = require('../utils/jwt');const { authenticate } = require('../middleware/auth');const router = express.Router();
<!-- Register -->router.post('/register', async (req, res) => { try { const { username, email, password } = req.body; <!-- Check if user exists --> const existingUser = await User.findOne({ $or: [{ username }, { email }] }); if (existingUser) { return res.status(400).json({ error: 'Username or email already exists' }); } <!-- Create user --> const user = await User.create({ username, email, password }); <!-- Generate token --> const token = generateToken(user); res.status(201).json({ message: 'User created successfully', token, user: { id: user._id, username, email, role: user.role } }); } catch (err) { res.status(500).json({ error: err.message }); }});
<!-- Login -->router.post('/login', async (req, res) => { try { const { username, password } = req.body; <!-- Find user --> const user = await User.findOne({ username }); if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); } <!-- Check password --> const isMatch = await user.comparePassword(password); if (!isMatch) { return res.status(401).json({ error: 'Invalid credentials' }); } <!-- Generate token --> const token = generateToken(user); res.json({ message: 'Login successful', token, user: { id: user._id, username: user.username, role: user.role } }); } catch (err) { res.status(500).json({ error: err.message }); }});
<!-- Get current user (protected) -->router.get('/me', authenticate, (req, res) => { res.json({ user: req.user });});
module.exports = router;5. Protected Routes Example
const { authenticate, authorize } = require('../middleware/auth');
<!-- Any authenticated user -->router.get('/profile', authenticate, (req, res) => { res.json({ message: 'Profile data', user: req.user });});
<!-- Admin only -->router.get('/admin', authenticate, authorize('admin'), (req, res) => { res.json({ message: 'Admin dashboard' });});Environment Variables (.env)
JWT_SECRET=your-super-secret-jwt-key-change-this-in-productionJWT_EXPIRES_IN=7dTwo Minute Drill
- JWT is a stateless authentication method – perfect for APIs and SPAs.
- Tokens consist of Header, Payload, and Signature.
- Store JWT secret securely in environment variables.
- Client sends token in Authorization header: `Bearer
`.
- Use middleware to authenticate and authorize requests.
Need more clarification?
Drop us an email at career@quipoinfotech.com
