Understanding JSON Web Tokens (JWT): A Complete Guide

JSON Web Tokens (JWT) have become the standard for authentication in modern web applications. This guide explains how JWTs work, their structure, best practices, and common security pitfalls.

What is a JWT?

A JSON Web Token (JWT, pronounced "jot") is a compact, URL-safe token format used to securely transmit information between parties as a JSON object. JWTs are commonly used for:

JWT Structure

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

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Each part is Base64URL-encoded:

1. Header

The header specifies the token type and the signing algorithm.

{
    "alg": "HS256",
    "typ": "JWT"
}
AlgorithmTypeDescription
HS256SymmetricHMAC with SHA-256 (shared secret)
HS384SymmetricHMAC with SHA-384
HS512SymmetricHMAC with SHA-512
RS256AsymmetricRSA with SHA-256 (public/private key)
ES256AsymmetricECDSA with SHA-256

2. Payload (Claims)

The payload contains the claims — statements about the user and additional metadata.

{
    "sub": "1234567890",
    "name": "John Doe",
    "email": "john@example.com",
    "role": "admin",
    "iat": 1516239022,
    "exp": 1516242622,
    "iss": "https://auth.example.com"
}

Registered Claims (Standard)

ClaimNameDescription
issIssuerWho issued the token
subSubjectWho the token is about (usually user ID)
audAudienceWho the token is intended for
expExpirationWhen the token expires (Unix timestamp)
nbfNot BeforeToken is not valid before this time
iatIssued AtWhen the token was created
jtiJWT IDUnique identifier for the token

3. Signature

The signature ensures the token hasn't been tampered with. It's created by signing the encoded header and payload with a secret key.

HMACSHA256(
    base64UrlEncode(header) + "." + base64UrlEncode(payload),
    secret
)

Advertisement

JWT Authentication Flow

  1. Login: User sends credentials (email/password) to the server
  2. Token Generation: Server verifies credentials and creates a JWT
  3. Token Storage: Client stores the JWT (usually in memory or httpOnly cookie)
  4. Authenticated Requests: Client sends JWT in the Authorization header
  5. Token Verification: Server verifies the JWT signature and checks claims

Implementation Examples

Node.js (jsonwebtoken)

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

// Generate token
function generateToken(user) {
    return jwt.sign(
        {
            sub: user.id,
            name: user.name,
            role: user.role
        },
        SECRET,
        { expiresIn: '1h' }
    );
}

// Verify token (middleware)
function authenticateToken(req, res, next) {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];
    
    if (!token) return res.sendStatus(401);
    
    jwt.verify(token, SECRET, (err, decoded) => {
        if (err) return res.sendStatus(403);
        req.user = decoded;
        next();
    });
}

// Protected route
app.get('/api/profile', authenticateToken, (req, res) => {
    res.json({ user: req.user });
});

Python (PyJWT)

import jwt
import datetime

SECRET = 'your-secret-key'

# Generate token
def generate_token(user_id, role):
    payload = {
        'sub': user_id,
        'role': role,
        'iat': datetime.datetime.utcnow(),
        'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    }
    return jwt.encode(payload, SECRET, algorithm='HS256')

# Verify token
def verify_token(token):
    try:
        payload = jwt.decode(token, SECRET, algorithms=['HS256'])
        return payload
    except jwt.ExpiredSignatureError:
        return None  # Token expired
    except jwt.InvalidTokenError:
        return None  # Invalid token

PHP

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$secret = getenv('JWT_SECRET');

// Generate token
$payload = [
    'sub' => $userId,
    'name' => $userName,
    'iat' => time(),
    'exp' => time() + 3600 // 1 hour
];
$token = JWT::encode($payload, $secret, 'HS256');

// Verify token
try {
    $decoded = JWT::decode($token, new Key($secret, 'HS256'));
    $userId = $decoded->sub;
} catch (Exception $e) {
    // Token invalid or expired
    http_response_code(401);
    exit('Unauthorized');
}

Access Token + Refresh Token Pattern

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

{
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "refreshToken": "dGhpcyBpcyBhIHJlZnJl...",
    "expiresIn": 900,
    "tokenType": "Bearer"
}

Security Best Practices

PracticeWhy
Use strong secrets (256+ bits)Prevents brute-force attacks on HMAC signatures
Set short expiration timesLimits the window of compromise if a token is stolen
Never store JWTs in localStorageVulnerable to XSS attacks
Use httpOnly cookies for storageNot accessible via JavaScript
Always validate exp and issPrevents expired/forged tokens
Use asymmetric keys (RS256) for distributed systemsServices can verify without knowing the secret
Never put sensitive data in the payloadJWT payload is only Base64-encoded, not encrypted
Implement token revocationAllows invalidating tokens before expiry
Validate the alg headerPrevents "alg: none" attacks
Important: JWTs are signed, not encrypted. Anyone can decode and read the payload. Never include passwords, credit card numbers, or other secrets in a JWT.

When NOT to Use JWTs

JWT vs. Session Cookies

FeatureJWTSession Cookie
StatelessYesNo (server stores session)
ScalabilityExcellent (no shared state)Requires session store
RevocationDifficult (needs blocklist)Easy (delete session)
Cross-domainEasy (via header)Difficult (cookie restrictions)
Mobile-friendlyYesLimited
Payload sizeLarger (contains claims)Small (just session ID)

Conclusion

JWTs are a powerful tool for authentication and authorization in modern applications. They enable stateless, scalable architectures and work seamlessly across different platforms and services. However, they come with security responsibilities — always follow best practices to keep your users and data safe.

Need to inspect a JWT payload? Use our JSON Beautifier to decode and format the JSON claims!