Expert ReviewedUpdated 2025developer
developer
11 min readOctober 27, 2024Updated Dec 17, 2025

JWT Tokens Explained: A Developer’s Complete Guide to JSON Web Tokens

Understand JWT structure, claims, signing algorithms, and security best practices. Learn how to implement stateless authentication with JSON Web Tokens.

JSON Web Tokens (JWTs) are the backbone of modern authentication. They enable stateless, scalable auth by embedding user data directly in a cryptographically signed token. This guide covers JWT structure, security best practices, and common pitfalls to avoid.

Key Takeaways

  • 1
    JWT = Header.Payload.Signature, each Base64URL-encoded; signed but not encrypted by default
  • 2
    Use RS256/ES256 in production (asymmetric); HS256 only for simple single-server setups
  • 3
    Short access tokens (15-30 min) + long-lived refresh tokens in HttpOnly cookies
  • 4
    Always validate: exp, iss, aud claims + whitelist algorithms (never trust token’s alg header)
  • 5
    JWTs are stateless; for revocation, track jti in Redis or use short expiry

1What Is a JWT?

A JWT is a compact, URL-safe token that contains claims (statements about a user or entity) encoded as JSON. It's digitally signed to ensure integrity and optionally encrypted for confidentiality.
JWT Structure: Header.Payload.Signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ← Header (Base64URL)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ← Payload (Base64URL)
                                                                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ← Signature
JWT components
PartContainsEncoded As
HeaderAlgorithm & token typeBase64URL JSON
PayloadClaims (user data, expiry)Base64URL JSON
SignatureHMAC or RSA signatureBase64URL binary
JWTs are encoded, not encrypted! Anyone can decode a JWT and read its payload. Never put sensitive data (passwords, credit cards) in a JWT unless using JWE (encrypted JWTs).

3The JWT Payload (Claims)

The payload contains claims—statements about the user and token metadata. There are three types of claims.
JWT claim types
Claim TypeDescriptionExamples
RegisteredStandard claims (RFC 7519)iss, sub, exp, iat, aud
PublicDefined in IANA registryname, email, picture
PrivateCustom app-specific claimsrole, org_id, permissions
{
  "sub": "user_123",        // Subject (user ID)
  "name": "John Doe",       // Public claim
  "email": "john@example.com",
  "role": "admin",          // Private claim
  "iat": 1704067200,        // Issued at (Unix timestamp)
  "exp": 1704153600,        // Expires at (Unix timestamp)
  "iss": "https://auth.example.com",  // Issuer
  "aud": "https://api.example.com"    // Audience
}
Standard registered claims
ClaimFull NamePurpose
issIssuerWho created/signed the token
subSubjectUser or entity ID
audAudienceIntended recipient (your API)
expExpirationToken expiry (Unix timestamp)
iatIssued AtWhen token was created
nbfNot BeforeToken not valid before this time
jtiJWT IDUnique token identifier (for revocation)

4The JWT Signature

The signature ensures the token hasn't been tampered with. It's created by signing the encoded header and payload.
// How signature is created (HS256 example)
signature = HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

// Verification process:
// 1. Split token into header.payload.signature
// 2. Recompute signature using header.payload + secret
// 3. Compare computed signature with provided signature
// 4. If match: token is valid and unmodified
Never accept "alg": "none"! This disables signature verification. Many JWT libraries have been vulnerable to this attack. Always validate the algorithm server-side.

Decode & Inspect JWTs

Paste any JWT to see its decoded header, payload, and signature.

Open JWT Decoder

5JWT Authentication Flow

Here's how JWT-based authentication typically works in a web application.
  1. 1User logs in with credentials (username/password, OAuth, etc.)
  2. 2Server validates credentials against database
  3. 3Server creates JWT with user claims and signs it
  4. 4Server returns JWT to client (in response body or cookie)
  5. 5Client stores JWT (localStorage, sessionStorage, or cookie)
  6. 6Client sends JWT with each API request (Authorization header)
  7. 7Server validates JWT signature and expiration
  8. 8Server extracts claims and processes request
// Client: Send JWT with requests
fetch('/api/protected', {
  headers: {
    'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIs...'
  }
});

// Server: Validate and extract claims (Node.js example)
import jwt from 'jsonwebtoken';

function authenticate(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ error: 'No token' });
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded; // { sub: "user_123", role: "admin", ... }
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

6Access Tokens vs Refresh Tokens

Production systems typically use two types of tokens with different lifespans.
Access vs refresh token comparison
AspectAccess TokenRefresh Token
PurposeAuthorize API requestsGet new access tokens
LifespanShort (15 min - 1 hour)Long (days - weeks)
StorageMemory or short-lived cookieHttpOnly secure cookie
Sent toEvery API requestToken refresh endpoint only
If stolenLimited damage windowCan be revoked server-side
// Token refresh flow
async function refreshAccessToken() {
  const response = await fetch('/api/auth/refresh', {
    method: 'POST',
    credentials: 'include' // Send refresh token cookie
  });
  
  if (response.ok) {
    const { accessToken } = await response.json();
    // Store new access token
    return accessToken;
  }
  
  // Refresh failed - redirect to login
  window.location.href = '/login';
}
Short access token expiry (15-30 min) limits damage if a token is stolen. The refresh token, stored securely, allows users to stay logged in without re-entering credentials.

7JWT Security Best Practices

JWTs are powerful but require careful implementation. Follow these security guidelines.
  • Use strong secrets: 256+ bits of entropy for HMAC keys
  • Set short expiration: Access tokens 15-60 min max
  • Validate all claims: Check exp, iss, aud server-side
  • Never trust
  • from token: Whitelist allowed algorithms
  • Use HTTPS only: Prevent token interception in transit
  • Store securely: HttpOnly cookies for refresh tokens, not localStorage
  • Implement revocation: Track token IDs (jti) for logout/compromise
// Secure JWT verification (Node.js)
const decoded = jwt.verify(token, secret, {
  algorithms: ['HS256'],      // Whitelist algorithms (prevent "none")
  issuer: 'https://auth.myapp.com',   // Validate issuer
  audience: 'https://api.myapp.com',  // Validate audience
  clockTolerance: 30          // Allow 30s clock skew
});

// Check for revocation (using jti claim)
const isRevoked = await checkTokenRevocationList(decoded.jti);
if (isRevoked) throw new Error('Token revoked');
localStorage is vulnerable to XSS attacks. If an attacker injects JavaScript, they can steal tokens. Use HttpOnly cookies for refresh tokens and consider in-memory storage for access tokens.

8Common JWT Mistakes

These security mistakes appear frequently in JWT implementations.
JWT security pitfalls
MistakeRiskFix
Long-lived access tokensExtended exposure windowUse 15-30 min expiry + refresh tokens
Weak/hardcoded secretsToken forgeryUse 256-bit random secrets from env vars
Not validating expExpired tokens acceptedAlways check expiration server-side
Trusting alg header"none" algorithm bypassWhitelist algorithms in verification
Storing in localStorageXSS token theftUse HttpOnly cookies for sensitive tokens
Not checking audienceToken misuse across appsValidate aud matches your API
No revocation mechanismCan't logout compromised usersTrack jti in Redis/DB for revocation
Example: Algorithm Confusion Attack

Scenario

Attacker changes alg from RS256 to HS256 and signs with public key

Solution

Never accept algorithm from token. Hardcode expected algorithm: jwt.verify(token, key, { algorithms: ['RS256'] })

Frequently Asked Questions

What is the difference between JWT, JWS, and JWE?
JWT is the umbrella term for JSON Web Tokens. JWS (JSON Web Signature) is a signed JWT—the most common type—where the payload is readable but tamper-proof. JWE (JSON Web Encryption) is an encrypted JWT where the payload is also confidential. Most ’JWTs’ are actually JWS tokens.
Where should I store JWTs on the client?
For access tokens: in-memory (JavaScript variable) is most secure against XSS. For refresh tokens: HttpOnly, Secure, SameSite=Strict cookies prevent XSS theft. Avoid localStorage for sensitive tokens as it’s accessible to any JavaScript on the page.
How do I revoke a JWT before it expires?
JWTs are stateless by design, so immediate revocation requires server-side tracking. Options: 1) Keep a revocation list of jti (token IDs) in Redis/database. 2) Use short expiry (15 min) so revocation happens naturally. 3) Store a ’valid since’ timestamp per user and reject older tokens.
Should I use HS256 or RS256?
RS256 (asymmetric) is preferred for production: the private key signs tokens, public key verifies them. This means verification can happen without exposing the signing secret. HS256 (symmetric) uses the same secret for both, so anyone who can verify can also forge tokens. Use HS256 only for simple single-server setups.
Why does my JWT contain user data if it can be decoded?
JWTs are signed, not encrypted (unless using JWE). The signature prevents tampering but not reading. This is by design: it allows clients to read user info without API calls. Never put sensitive data (passwords, payment info) in a JWT. Use claims like user ID, email, and roles that are safe to expose.