JWT Complete Guide: Structure, Signing & Security Best Practices

JSON Web Tokens (JWTs) have become the de facto standard for stateless authentication in modern web applications and APIs. Understanding how JWTs work — and their security implications — is essential for any developer building authentication systems. This guide covers everything from the token structure to signing algorithms and common security pitfalls.

What Is a JWT?

A JWT (pronounced "jot") is a compact, URL-safe token format defined in RFC 7519. It allows you to transmit claims (pieces of information) between parties as a JSON object that is digitally signed and optionally encrypted.

A JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNjE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

It consists of three Base64url-encoded parts separated by dots: Header.Payload.Signature.

JWT Structure

1. Header

The header typically contains two fields: the signing algorithm and the token type.

{
  "alg": "HS256",
  "typ": "JWT"
}

This JSON is Base64url-encoded to form the first segment of the token.

2. Payload (Claims)

The payload contains the claims — statements about the user and additional metadata. There are three types of claims:

Registered claims (standardized by RFC 7519):

  • iss (Issuer) — Who issued the token
  • sub (Subject) — The user the token represents
  • aud (Audience) — The intended recipient
  • exp (Expiration) — When the token expires (Unix timestamp)
  • iat (Issued At) — When the token was created
  • nbf (Not Before) — Token is not valid before this time
  • jti (JWT ID) — Unique identifier to prevent replay
{
  "sub": "user123",
  "name": "Alice Johnson",
  "email": "alice@example.com",
  "role": "admin",
  "iat": 1710230400,
  "exp": 1710316800
}
⚠️ The payload is NOT encrypted. Anyone can decode the Base64url payload and read the claims. Never put sensitive data (passwords, credit card numbers) in a JWT payload.

3. Signature

The signature verifies that the token hasn't been tampered with. It is computed by signing the encoded header and payload:

// HMAC-SHA256 signature
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

Signing Algorithms

JWT supports multiple signing algorithms:

Symmetric (Shared Secret)

  • HS256 — HMAC with SHA-256 (most common for simple apps)
  • HS384 — HMAC with SHA-384
  • HS512 — HMAC with SHA-512

Both the issuer and verifier share the same secret key.

Asymmetric (Public/Private Key)

  • RS256 — RSA with SHA-256 (widely used)
  • ES256 — ECDSA with P-256 curve (shorter keys, fast)
  • EdDSA — Edwards-curve DSA (modern, fast)

The issuer signs with a private key; anyone can verify with the public key.

💡 Which algorithm to choose? Use HS256 for simple single-server apps. Use RS256 or ES256 for microservices or when third parties need to verify tokens without knowing the signing key.

Creating JWTs in Code

# Python with PyJWT
import jwt
import datetime

payload = {
    "sub": "user123",
    "name": "Alice",
    "iat": datetime.datetime.now(datetime.timezone.utc),
    "exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=24)
}

# Sign with HMAC-SHA256
token = jwt.encode(payload, "your-secret-key", algorithm="HS256")
print(token)

# Verify and decode
decoded = jwt.decode(token, "your-secret-key", algorithms=["HS256"])
print(decoded["name"])  # "Alice"
// Node.js with jsonwebtoken
const jwt = require('jsonwebtoken');

const token = jwt.sign(
  { sub: 'user123', name: 'Alice' },
  'your-secret-key',
  { expiresIn: '24h' }
);

// Verify
const decoded = jwt.verify(token, 'your-secret-key');
console.log(decoded.name); // "Alice"

JWT Authentication Flow

  1. User sends credentials (username/password) to login endpoint
  2. Server validates credentials and creates a JWT
  3. Server returns the JWT to the client
  4. Client stores the JWT (typically in memory or httpOnly cookie)
  5. Client sends the JWT in the Authorization: Bearer <token> header with each request
  6. Server validates the JWT signature and checks claims (expiry, audience, etc.)

Security Best Practices

⚠️ Common JWT vulnerabilities can lead to authentication bypass. Follow these practices carefully:
  1. Always validate the signature — Never trust an unverified token
  2. Check the alg header — Reject "alg": "none" tokens. Whitelist allowed algorithms on the server
  3. Set short expiration times — Use exp claims of 15 minutes to 24 hours. Use refresh tokens for longer sessions
  4. Use strong secrets — For HMAC, use at least 256 bits of entropy. Never use simple strings like "secret"
  5. Store tokens securely — Prefer httpOnly cookies over localStorage (prevents XSS theft)
  6. Don't store sensitive data in the payload — The payload is only encoded, not encrypted
  7. Validate all claims — Check iss, aud, exp, and nbf on every request
  8. Implement token revocation — Use a deny-list for critical security events (password change, logout)
# Python: secure JWT verification
decoded = jwt.decode(
    token,
    "your-secret-key",
    algorithms=["HS256"],       # Whitelist allowed algorithms
    options={
        "require": ["exp", "iat", "sub"],  # Require essential claims
        "verify_exp": True,
        "verify_iat": True,
    },
    issuer="https://api.example.com",     # Validate issuer
    audience="https://app.example.com",   # Validate audience
)

🛠️ Decode & Inspect JWT Tokens Online

Open JWT Tool →

Further Reading