Overview

JSON Web Tokens (JWTs) are the standard way to authenticate users in stateless APIs. This tutorial explains the three-part JWT structure, walks through a login-and-protected-route flow, and covers the security pitfalls that cause real-world vulnerabilities.

What Is a JWT?

A JWT is a compact, URL-safe string with three parts separated by dots:

header.payload.signature
Part Content Example
Header Token type and signing algorithm {"alg":"HS256","typ":"JWT"}
Payload Claims such as user ID and expiry {"sub":"123","exp":1735689600}
Signature HMAC of header + payload using a secret HMACSHA256(base64(header) + "." + base64(payload), secret)

The payload is Base64URL-encoded, not encrypted. Anyone can read it. Never put passwords or secrets in a JWT payload.

Typical JWT Flow

  1. User sends credentials to POST /api/auth/login.
  2. Server verifies credentials and returns a signed JWT.
  3. Client stores the token (memory, secure cookie, or local storage).
  4. Client sends the token in the Authorization: Bearer <token> header on subsequent requests.
  5. Server verifies the signature and expiry on each request.

Node.js Implementation Example

const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;

// Sign a token on login
function issueToken(userId) {
  return jwt.sign(
    { sub: userId },
    SECRET,
    { expiresIn: '1h' }
  );
}

// Verify middleware
function authenticate(req, res, next) {
  const header = req.headers.authorization || '';
  const token = header.startsWith('Bearer ') ? header.slice(7) : null;

  if (!token) {
    return res.status(401).json({ error: 'Missing token' });
  }

  try {
    const payload = jwt.verify(token, SECRET);
    req.userId = payload.sub;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
}

Install the jsonwebtoken package from the jsonwebtoken npm page.

Common Claims

Claim Meaning
sub Subject, typically the user ID
exp Expiration time as a Unix timestamp
iat Issued at time
iss Issuer
aud Audience

Security Pitfalls

Pitfall Risk Mitigation
Storing JWT in localStorage XSS can steal the token Use HttpOnly, Secure cookies
Not verifying the algorithm Algorithm confusion attack Explicitly specify allowed algorithms
Long expiry without refresh Stolen token valid for weeks Use short-lived access tokens plus refresh tokens
Putting sensitive data in payload Payload is readable Only store non-sensitive identifiers

Refresh Token Pattern

Issue two tokens: a short-lived access token (15 minutes) and a long-lived refresh token (7 days). When the access token expires, the client sends the refresh token to POST /api/auth/refresh to get a new access token. Store refresh tokens in the database so they can be revoked.