What is a JSON Web Token?

A JSON Web Token (JWT) is a compact, self-contained way to represent claims between two parties. JWTs are widely used for authentication and authorization in modern web APIs — once a user logs in, the server issues a JWT that the client sends with every subsequent request to prove its identity.

A JWT consists of three Base64URL-encoded parts separated by dots: header.payload.signature

Anatomy of a JWT

  • Header: Specifies the token type (JWT) and signing algorithm (e.g., HS256, RS256).
  • Payload: Contains the claims — data about the user and the token (e.g., sub, exp, iat, custom roles).
  • Signature: A cryptographic signature that verifies the token hasn't been tampered with.

Example decoded payload:

{
  "sub": "user_123",
  "email": "dev@example.com",
  "role": "admin",
  "iat": 1725000000,
  "exp": 1725086400
}

Implementing JWT Authentication (Node.js Example)

1. Issuing a Token on Login

const jwt = require('jsonwebtoken');

function login(req, res) {
  const user = authenticateUser(req.body); // your auth logic
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });

  const token = jwt.sign(
    { sub: user.id, email: user.email, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '1h' }
  );

  res.json({ token });
}

2. Verifying a Token on Protected Routes

function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }

  const token = authHeader.split(' ')[1];
  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid or expired token' });
  }
}

JWT Security Best Practices

  1. Use strong, random secrets. Your JWT_SECRET should be at least 256 bits of randomness. Never hardcode it.
  2. Set short expiry times. Access tokens should expire in minutes to hours, not days. Use refresh tokens for longer sessions.
  3. Store tokens securely. Prefer HttpOnly cookies over localStorage to mitigate XSS attacks.
  4. Use asymmetric algorithms for distributed systems. RS256 (RSA) allows public key verification without sharing the private signing key.
  5. Validate all claims. Always check exp (expiry), iat (issued at), and iss (issuer) in your verification logic.
  6. Don't put sensitive data in the payload. JWTs are Base64-encoded, not encrypted. Anyone can decode the payload.

Common JWT Mistakes to Avoid

  • Accepting the none algorithm — always explicitly whitelist allowed algorithms in your verify call.
  • Never revoking tokens — implement a token blacklist or use short-lived tokens with refresh rotation.
  • Using the same secret across environments — production, staging, and development should each have unique secrets.

When Not to Use JWTs

JWTs are not always the right tool. For simple session management in a monolithic app with a single server, traditional server-side sessions with a session store (Redis, database) are often simpler and easier to revoke. JWTs shine in distributed systems, microservices, and cross-domain authentication where stateless verification is valuable.