JWT (JSON Web Token) is one of the most common authentication mechanisms in modern web applications. But it's also one of the most misunderstood. In this post, I'll explain exactly how it works and how to implement it properly.
What is a JWT?
A JWT is a compact, self-contained token made up of three Base64URL-encoded parts separated by dots:
header.payload.signature
Header — algorithm and token type:
{ "alg": "HS256", "typ": "JWT" }
Payload — your claims (user data):
{ "userId": "abc123", "email": "user@example.com", "iat": 1704067200, "exp": 1704070800 }
Signature — verifies the token hasn't been tampered with:
HMACSHA256(base64(header) + "." + base64(payload), SECRET_KEY)
Generating a Token
// npm install jsonwebtoken
import jwt from 'jsonwebtoken'
const token = jwt.sign(
{ userId: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '2h' }
)
Verifying a Token (Middleware)
export function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ message: 'No token provided' })
}
const token = authHeader.split(' ')[1]
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET)
req.user = decoded
next()
} catch (err) {
return res.status(401).json({ message: 'Invalid or expired token' })
}
}
Storing Tokens Safely
Don't store JWTs in localStorage — they're vulnerable to XSS attacks. Use one of these instead:
- httpOnly cookies — the safest option, inaccessible to JavaScript
- Memory (React state) — safe from XSS, but lost on refresh
// Setting an httpOnly cookie in Express
res.cookie('token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 2 * 60 * 60 * 1000 // 2 hours
})
The Bottom Line
JWTs are powerful but come with tradeoffs. Keep them short-lived, store them carefully, and always verify on the server. Never trust the client to enforce authorization.