Introduction
Authentication is the process of verifying that a user is who they claim to be. In web applications this typically happens once at login, when the client sends credentials (like an email and password) to the server and receives proof of identity in return. A JSON Web Token (JWT) is a compact, URL-safe token format commonly used in Express APIs to represent this proof. Once issued, the client attaches the JWT to subsequent requests, and the server verifies it without needing to look up a session in a database, making JWT-based authentication stateless and easy to scale across multiple servers.
Cricket analogy: Presenting your JWT on each request is like a player showing their accreditation badge at the stadium gate every match day — security checks it instantly without phoning the board office to re-verify who you are each time.
Syntax
const jwt = require('jsonwebtoken');
// Signing a token after successful login
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
// Verifying a token on a protected route
function authenticate(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, decoded) => {
if (err) return res.status(403).json({ error: 'Invalid or expired token' });
req.user = decoded;
next();
});
}Explanation
A JWT has three dot-separated parts: header.payload.signature. The header declares the token type and signing algorithm (commonly HS256). The payload is a Base64Url-encoded JSON object holding claims such as userId, role, and expiry (exp) — anyone can decode and read the payload, so it must never contain secrets like passwords. The signature is generated by hashing the header and payload together with a secret key (HMAC) or a private key (RSA/ECDSA); it lets the server detect if the token was tampered with. It is important to understand that JWT signing provides integrity and authenticity, not encryption — the payload is readable by anyone who intercepts it, so sensitive data should be kept out of the token entirely.
Cricket analogy: A JWT is like a scorecard with three parts — the match format label (header), the visible ball-by-ball tally anyone in the stands can read (payload), and the umpire's official seal proving it wasn't altered (signature) — but the seal doesn't hide the score from spectators.
Example
const express = require('express');
const jwt = require('jsonwebtoken');
const router = express.Router();
router.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
const validPassword = user && (await bcrypt.compare(password, user.passwordHash));
if (!user || !validPassword) {
return res.status(401).json({ error: 'Invalid email or password' });
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
});
router.get('/profile', authenticate, (req, res) => {
res.json({ userId: req.user.userId, message: 'Protected profile data' });
});Output
A successful login returns a JSON response like { "token": "eyJhbGciOiJIUzI1NiIs..." }. When the client calls GET /profile with the header Authorization: Bearer <token>, the middleware decodes and verifies the signature; if valid, it attaches the decoded payload to req.user and the route responds with the protected data. If the token is missing, expired, or has an invalid signature, the server responds with a 401 or 403 status instead of leaking any protected information.
Cricket analogy: Just as a player without a valid accreditation badge is turned away at the gate with a simple "access denied" rather than being told exactly which security detail flagged them, a missing or invalid JWT gets a flat 401/403 without revealing internal verification details.
Key Takeaways
- A JWT consists of header.payload.signature, and only the signature is cryptographically protected against tampering.
- The payload is encoded, not encrypted — never store passwords or secrets inside a JWT.
- Always set an expiration (exp) claim and verify the signature with a strong, secret key stored in environment variables.
- JWTs enable stateless authentication, which scales well but makes immediate token revocation harder than with server-side sessions.
Practice what you learned
1. What are the three parts of a JWT, in order?
2. Why should sensitive data like a plaintext password never be placed inside a JWT payload?
3. What is the primary purpose of the JWT signature?
4. What happens when jwt.verify() is called on an expired token?
Was this page helpful?
You May Also Like
Password Hashing with bcrypt
Understand why passwords must be hashed with bcrypt rather than stored in plain text or hashed with a fast general-purpose algorithm.
Session vs Token Authentication
Compare stateful session-based authentication with stateless token-based authentication and when to use each approach.
Securing Express Apps (Helmet, CORS, Rate Limiting)
Harden Express applications with secure HTTP headers, controlled cross-origin access, and rate limiting to mitigate common attacks.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics