Authentication10 min read

JWT Guide: Understanding JSON Web Tokens & Authentication

Master JSON Web Tokens (JWT) with this comprehensive guide. Learn how JWTs work, how to decode them, best practices for security, and when to use them in your applications.

Updated December 2, 2025

What is JWT?

JWT (JSON Web Token) is a compact, URL-safe way to securely transmit information between parties. It's a token that contains encoded claims (statements about an entity and additional metadata).

Think of it like a digital ID card:

  • Self-contained: All user info is inside the token
  • Digitally signed: Can't be forged or modified
  • Stateless: No need to query a database each time
  • Transferable: Can be sent via HTTP headers or cookies

JWTs are commonly used for authentication, authorization, and secure information exchange in APIs.

JWT Structure & Format

A JWT consists of three parts separated by dots (.). Here's a real example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Part 1: Header

Base64-encoded JSON describing the token type and hashing algorithm:

{
  "alg": "HS256",  // Algorithm (HMAC SHA-256)
  "typ": "JWT"     // Token type
}

Part 2: Payload (Claims)

Base64-encoded JSON containing the actual data:

{
  "sub": "1234567890",    // Subject (user ID)
  "name": "John Doe",     // User name
  "email": "[email protected]",
  "iat": 1516239022,      // Issued at (timestamp)
  "exp": 1516242622,      // Expiration time
  "admin": true           // Custom claims
}

Standard claims include: iss (issuer), sub (subject), aud (audience), iat (issued at), exp (expiration).

Part 3: Signature

Cryptographic signature ensuring the token hasn't been tampered with:

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

Only the server with the secret key can create or verify the signature.

How JWT Authentication Works

Here's the typical JWT authentication flow:

  1. User logs in with username & password
  2. Server validates credentials against database
  3. Server creates JWT with user data and signs it
  4. Token sent to client (usually in response body)
  5. Client stores token (usually in localStorage or sessionStorage)
  6. Client sends token with each request in Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
  1. Server verifies token on each protected endpoint
  2. Server grants access if signature is valid and not expired

Key advantage: No need to query the database for every request. The token itself contains all necessary user information.

Decoding JWT Tokens

Decoding is simple: split by "." and base64-decode each part:

JavaScript Example:

function decodeJWT(token) {
  const parts = token.split('.');
  const header = JSON.parse(atob(parts[0]));
  const payload = JSON.parse(atob(parts[1]));
  
  return {
    header,
    payload,
    signature: parts[2]
  };
}

const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
const decoded = decodeJWT(jwt);
console.log(decoded.payload); // { sub: '123', name: 'John', ... }

Python Example:

import jwt
import json
import base64

token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'

# Method 1: Using jwt library (simpler)
decoded = jwt.decode(token, options={"verify_signature": False})
print(decoded)

# Method 2: Manual decoding
parts = token.split('.')
header = json.loads(base64.urlsafe_b64decode(parts[0] + '=='))
payload = json.loads(base64.urlsafe_b64decode(parts[1] + '=='))
print(payload)

⚠️ Important: Base64 is NOT encryption. Decoding doesn't require the secret key. Never put sensitive passwords or credit card info in JWT claims!

Verifying JWT Signatures

Decoding and verifying are different. To verify that a JWT is legitimate, you must check the signature:

JavaScript Example:

// Install: npm install jsonwebtoken
const jwt = require('jsonwebtoken');

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
const secret = 'your-secret-key';

try {
  const decoded = jwt.verify(token, secret);
  console.log('Token is valid:', decoded);
} catch (error) {
  console.error('Token is invalid:', error.message);
  // Possible errors: TokenExpiredError, JsonWebTokenError
}

Python Example:

import jwt

token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
secret = 'your-secret-key'

try:
    decoded = jwt.decode(token, secret, algorithms=['HS256'])
    print('Token is valid:', decoded)
except jwt.ExpiredSignatureError:
    print('Token has expired')
except jwt.InvalidTokenError:
    print('Token is invalid')

The verification process recalculates the signature using the secret key and compares it with the signature in the token. If they match, the token is valid and hasn't been tampered with.

Security Best Practices

1. Use HTTPS Always

Always transmit JWTs over HTTPS, never HTTP. JWTs are signed but not encrypted, so they can be read if transmitted insecurely.

2. Set Expiration Times

Always include an exp claim and refresh tokens regularly:

const payload = {
  sub: user.id,
  name: user.name,
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + (15 * 60) // 15 minutes
};

3. Use Strong Secret Keys

  • Use at least 256-bit keys (32 bytes)
  • Rotate keys periodically
  • Never commit secrets to version control
  • Store in environment variables or secret managers

4. Implement Refresh Tokens

Use short-lived access tokens (15 min) with longer-lived refresh tokens (7 days):

// Access token: 15 minutes
const accessToken = jwt.sign(
  { sub: user.id },
  secret,
  { expiresIn: '15m' }
);

// Refresh token: 7 days
const refreshToken = jwt.sign(
  { sub: user.id, type: 'refresh' },
  refreshSecret,
  { expiresIn: '7d' }
);

5. Avoid Sensitive Data

Don't include passwords, credit cards, or PII in JWT claims. Only include data you don't mind a user seeing (it's just base64 encoded, not encrypted).

6. Validate Claims

Always validate all claims on the server:

const decoded = jwt.verify(token, secret);

// Validate claims
if (decoded.exp < Math.floor(Date.now() / 1000)) {
  throw new Error('Token expired');
}

if (!decoded.sub) {
  throw new Error('Invalid token: missing subject');
}

if (decoded.aud !== expectedAudience) {
  throw new Error('Invalid audience');
}

When to Use JWT

✅ Good Use Cases

  • REST APIs: Stateless authentication between client and server
  • Microservices: Pass authentication context between services
  • Single Page Apps (SPAs): Browser-based authentication
  • Mobile Apps: Lightweight, efficient authentication
  • Cross-Domain: CORS-friendly authentication
  • Third-party integrations: OAuth authorization flows

❌ Avoid JWT For

  • Traditional server-side sessions: Use cookies instead
  • Sensitive operations: Combine with other verification methods
  • Data that changes frequently: Session claims might become stale
  • Immediate revocation: JWTs can't be revoked until expiration (use blacklist if needed)

Standard JWT Claims Reference

ClaimDescription
issIssuer (who created the token)
subSubject (user ID)
audAudience (who should receive this token)
expExpiration time (Unix timestamp)
iatIssued at (Unix timestamp)
nbfNot before (token valid from this time)

Related Tools

Try Our JWT Decoder

Instantly decode and verify JWT tokens with our free JWT Decoder tool.

Open JWT Decoder →

Stay Updated

Get weekly developer tips, tool updates, and programming tutorials delivered to your inbox. No spam, just valuable content.