Skip to main content
About

JWT Tokens Explained: How JSON Web Tokens Secure APIs and Authentication

Traditional session-based authentication requires the server to maintain a session store. Every request hits the database to validate the session. JWT (JSON Web Token) flips this: the token itself contains everything needed for verification. The server doesn't need to store sessions. Just sign the token with a secret, send it to the client, and verify the signature on each request. This stateless approach scales beautifully and powers modern APIs, mobile apps, and microservices.

This guide covers JWT structure, how they work, when to use them, security best practices, and real-world implementation examples.

What is a JWT?

JWT (JSON Web Token) is a self-contained token that encodes information about a user or entity in a cryptographically signed format. It's three Base64-encoded parts separated by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ↓ [HEADER].[PAYLOAD].[SIGNATURE]

JWT Structure

1. Header

The header specifies the token type and signing algorithm:

{ "alg": "HS256", // Algorithm: HMAC SHA-256 "typ": "JWT" // Token type } Base64 encoded: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. Payload (Claims)

The payload contains claims—data about the user:

{ "sub": "user123", // Subject (user ID) "email": "john@example.com", // Custom claim "role": "admin", // Custom claim "iat": 1516239022, // Issued at (timestamp) "exp": 1516325422 // Expiration time } Base64 encoded: eyJzdWIiOiJ1c2VyMTIzIiwiZW1haWwiOiJqb2huQGV4YW1wbGUuY29tIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYzMjU0MjJ9

Standard Claims:

  • sub: Subject (typically user ID)
  • iat: Issued at (seconds since epoch)
  • exp: Expiration time (seconds since epoch)
  • aud: Audience (who the token is for)
  • iss: Issuer (who created the token)

3. Signature

The signature proves the token hasn't been tampered with:

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret_key ) Result: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c Only the server with secret_key can create valid signatures.

How JWT Authentication Works

1. User logs in with credentials POST /login { username: "john", password: "secret" } 2. Server verifies credentials, creates JWT token = sign({ sub: "user123", email: "john@..." }, secret) 3. Server sends token to client Response: { token: "eyJhbGc..." } 4. Client stores token (localStorage, sessionStorage, cookie) 5. Client sends token with each request GET /api/profile Authorization: Bearer eyJhbGc... 6. Server verifies signature (no database lookup needed) If signature valid and token not expired → request allowed 7. Stateless verification complete (no session storage needed)

JWT vs. Sessions

Feature JWT Session
Storage Stateless (client-side) Stateful (server)
Scalability Scales easily (no DB) Requires shared store
Mobile-friendly ✅ Perfect ❌ Poor (cookies)
Cross-domain ✅ Works easily ❌ CORS issues
Instant logout ❌ Hard (token valid until exp) ✅ Easy (delete session)

JWT Implementation

Creating and Verifying JWTs (Node.js)

const jwt = require('jsonwebtoken'); const secret = process.env.JWT_SECRET; // 1. Create JWT on login app.post('/login', (req, res) => { const user = { id: 123, email: 'john@example.com' }; const token = jwt.sign(user, secret, { expiresIn: '1h' // Token valid for 1 hour }); res.json({ token }); }); // 2. Verify JWT on protected routes app.get('/api/profile', (req, res) => { const token = req.headers.authorization?.split(' ')[1]; try { const decoded = jwt.verify(token, secret); // Token valid, decoded contains user data res.json({ user: decoded }); } catch (err) { res.status(401).json({ error: 'Invalid token' }); } });

Client-side Usage (JavaScript)

// 1. Login and store token async function login(email, password) { const res = await fetch('/login', { method: 'POST', body: JSON.stringify({ email, password }) }); const { token } = await res.json(); localStorage.setItem('jwt_token', token); } // 2. Send token with requests async function fetchProtectedResource() { const token = localStorage.getItem('jwt_token'); const res = await fetch('/api/profile', { headers: { 'Authorization': `Bearer ${token}` } }); return res.json(); }

JWT Security Best Practices

  • ✅ Use HTTPS always (prevent token theft in transit)
  • ✅ Keep secret key safe (environment variables, key management services)
  • ✅ Use short expiration times (1 hour or less)
  • ✅ Implement refresh tokens for longer sessions
  • ✅ Store tokens securely (httpOnly cookies, not localStorage)
  • ✅ Verify signature on every request
  • ✅ Use RS256 (RSA) for multi-service scenarios
  • ✅ Never trust unverified JWTs

Common JWT Mistakes

  • ❌ Storing sensitive data in JWT (visible in Base64)
  • ❌ Using weak secret keys (use random, long secrets)
  • ❌ Not verifying signature (defeats the purpose)
  • ❌ Storing JWT in localStorage (vulnerable to XSS)
  • ❌ No expiration time (compromised tokens valid forever)
  • ❌ Ignoring token expiration in client code

Refresh Token Pattern

For better security, use short-lived access tokens + long-lived refresh tokens:

// Short-lived access token (15 minutes) const accessToken = jwt.sign(user, secret, { expiresIn: '15m' }); // Long-lived refresh token (7 days, stored securely) const refreshToken = jwt.sign(user, refreshSecret, { expiresIn: '7d' }); // When access token expires, use refresh token to get new one POST /refresh Body: { refreshToken } Response: { accessToken } (new 15-min token)

Key Takeaways

  • Three parts: Header.Payload.Signature (all Base64-encoded)
  • Stateless: Server doesn't store anything (scales infinitely)
  • Signed, not encrypted: Can be read but not forged
  • Use expiration: Always set exp claim
  • HTTPS required: Tokens vulnerable in transit without encryption
  • Perfect for APIs: Better than cookies for cross-domain requests

Next Steps

  1. Implement JWT authentication in your API
  2. Use a library (jsonwebtoken in Node, PyJWT in Python)
  3. Set appropriate expiration times
  4. Implement refresh token flow for better security
  5. Use jwt.io to debug and understand token structure
  6. Test token verification and expiration handling

JWT tokens are foundational to modern API authentication. Understanding their structure and security implications is essential for building scalable, secure web applications.