JSON Web Token (JWT)

Understanding JSON Web Tokens (JWT) for secure authentication and information exchange.

JSON Web Tokens (JWT) provide a compact, URL-safe means of representing claims securely between two parties. They are commonly used for authentication and authorization in web applications.

What is JWT?

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.

JWTs can be:

  • Signed using a secret (with the HMAC algorithm) or a public/private key pair (RSA or ECDSA)
  • Encrypted to also provide secrecy between parties

JWT Structure

A JWT consists of three parts separated by dots (.):

xxxxx.yyyyy.zzzzz
  • Header (xxxxx): Contains token type and signing algorithm
  • Payload (yyyyy): Contains claims (user data and metadata)
  • Signature (zzzzz): Ensures the token hasn’t been altered

JWT Token Structure Visualization

graph LR
    A[JWT Token] --> B[Header]
    A --> C[Payload]
    A --> D[Signature]
    
    B --> B1[Base64Url Encoded]
    C --> C1[Base64Url Encoded]
    
    B1 --> E["{ 
        alg: 'HS256',
        typ: 'JWT' 
    }"]
    
    C1 --> F["{ 
        sub: '1234567890',
        name: 'John Doe',
        iat: 1516239022,
        exp: 1516242622
    }"]
    
    D --> G["HMACSHA256(
        base64UrlEncode(header) + '.' +
        base64UrlEncode(payload),
        secret)"]

How JWT Works

JWT authentication follows a simple flow:

  1. User logs in with credentials
  2. Server validates credentials and generates a JWT
  3. JWT is returned to the client
  4. Client stores the JWT (typically in local storage or a cookie)
  5. Client sends the JWT with subsequent requests
  6. Server validates the JWT signature and processes the request

JWT Authentication Flow

sequenceDiagram
    participant User
    participant Client
    participant Server
    
    User->>Client: Enter credentials
    Client->>Server: Authentication request
    Note right of Server: Validate credentials
    Server->>Server: Generate JWT with payload and signature
    Server->>Client: Return JWT
    Note left of Client: Store JWT
    
    loop For each protected request
        Client->>Server: Request with JWT in Authorization header
        Note right of Server: Validate JWT signature
        Note right of Server: Check expiration
        Server->>Client: Protected resource/response
    end

JWT Claims

Claims are statements about an entity (typically the user) and additional metadata. There are three types of claims:

  1. Registered Claims: Predefined claims providing a set of useful, interoperable claims

    • iss (issuer)
    • sub (subject)
    • aud (audience)
    • exp (expiration time)
    • nbf (not before)
    • iat (issued at)
    • jti (JWT ID)
  2. Public Claims: Custom claims defined by those using JWTs

  3. Private Claims: Custom claims created to share information between parties

JWT Implementation Example

Here’s a simplified example of generating and validating a JWT:

Server-side: Generating a JWT (Node.js)

const jwt = require('jsonwebtoken');

// User authentication function
function authenticate(username, password) {
  // Validate user credentials (simplified)
  if (isValidUser(username, password)) {
    // Create token payload
    const payload = {
      sub: getUserId(username),
      name: username,
      role: getUserRole(username),
      iat: Math.floor(Date.now() / 1000),
      exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour expiration
    };
    
    // Generate and sign token
    const token = jwt.sign(payload, process.env.JWT_SECRET);
    return token;
  }
  throw new Error('Authentication failed');
}

Server-side: Validating a JWT (Node.js)

const jwt = require('jsonwebtoken');

// Middleware to validate JWT
function verifyToken(req, res, next) {
  // Get auth header value
  const bearerHeader = req.headers['authorization'];
  
  if (bearerHeader) {
    // Extract token from "Bearer <token>"
    const token = bearerHeader.split(' ')[1];
    
    jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
      if (err) {
        return res.status(403).json({ message: 'Invalid or expired token' });
      }
      
      // Add decoded user info to request object
      req.user = decoded;
      next();
    });
  } else {
    res.status(401).json({ message: 'Access denied. No token provided.' });
  }
}

JWT Storage Security Diagram

graph TD
    A[JWT Storage Options] --> B[Browser Storage]
    A --> C[HTTP Only Cookie]
    
    B --> D[localStorage]
    B --> E[sessionStorage]
    B --> F[Memory Variable]
    
    D --> G[Pros: Simple, Persists]
    D --> H[Cons: XSS Vulnerable]
    
    E --> I[Pros: Session-bound]
    E --> J[Cons: XSS Vulnerable]
    
    F --> K[Pros: Not accessible via XSS]
    F --> L[Cons: Lost on refresh]
    
    C --> M[Pros: XSS Protected, CSRF can be mitigated]
    C --> N[Cons: Server config required]
    
    style C fill:#c9e7f9
    style M fill:#c9f9d9

Best Practices for JWT

  1. Use HTTPS: Always transmit JWTs over HTTPS to prevent token theft through network eavesdropping

  2. Set Proper Expiration: Use short expiration times and implement token refresh

    graph LR
        A[Token Types] --> B[Access Token]
        A --> C[Refresh Token]
        B --> D[Short lived: 15min - 1hr]
        C --> E[Longer lived: days/weeks]
        B --> F[Frequent validation]
        C --> G[Stored securely server-side]
  3. Validate All Claims: Check issuer, audience, expiration, and all other relevant claims

  4. Keep Tokens Small: Include only necessary data in the payload to maintain performance

  5. Secure Storage: Store tokens securely, preferably in HttpOnly cookies for web applications

  6. Implement Token Revocation: Have a strategy for invalidating tokens when needed (logout, password change)

  7. Use Strong Keys: Use strong, randomly generated secrets for signing tokens

JWT vs Session Authentication

graph TB
    subgraph "JWT Authentication"
        A1[Client] -->|1. Login| B1[Server]
        B1 -->|2. JWT Token| A1
        A1 -->|3. Request with JWT| B1
        B1 -->|4. Validate JWT & Respond| A1
    end
    
    subgraph "Session Authentication"
        A2[Client] -->|1. Login| B2[Server]
        B2 -->|2. Session ID Cookie| A2
        A2 -->|3. Request with Cookie| B2
        B2 -->|4. Lookup Session & Respond| B2
    end
    
    C[Comparison]
    C --> D[JWT: Stateless, no session storage]
    C --> E[Session: Stateful, session storage required]
    C --> F[JWT: Larger request size]
    C --> G[Session: Smaller request size]
    C --> H[JWT: Decentralized validation possible]
    C --> I[Session: Centralized validation required]

Common JWT Security Vulnerabilities

  1. None Algorithm Attack: Ensure your JWT library rejects tokens with the “none” algorithm

  2. Algorithm Confusion: Ensure proper algorithm verification is implemented

  3. Missing Signature Validation: Always validate signatures, never trust just the presence of a token

  4. Weak Secrets: Use strong, random secrets or keys for token signing

  5. Token Sidejacking: Implement additional security measures to prevent token theft

Further Reading


Last modified March 27, 2025: Edit members.yaml (21070ed)