Security

JWT Tokens Decoded: Structure, Security, and Best Practices

Understanding JSON Web Tokens: the three parts of a JWT, how verification works, and security considerations for token-based authentication.

HandyUtils January 3, 2026 5 min read

JSON Web Tokens (JWTs) are everywhere in modern authentication. Understanding how they work—and their security implications—is crucial for any developer building APIs or web applications.

What is a JWT?

A JWT (pronounced "jot") is a compact, URL-safe way to represent claims between two parties. It's commonly used for:

  • Authentication: After login, users receive a JWT to prove their identity
  • Authorization: JWTs can contain permissions/roles
  • Information exchange: Secure data transfer between services

A JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Notice the three parts separated by dots.

The Three Parts

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

The header specifies:

  • alg: The signing algorithm (HS256, RS256, etc.)
  • typ: Token type (always "JWT")

This is Base64URL encoded to become:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. Payload (Claims)

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

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

Base64URL encoded:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0

3. Signature

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

The signature ensures the token hasn't been tampered with. It's created by:

  1. Combining the encoded header and payload with a dot
  2. Signing with the secret key using the specified algorithm

Base64URL Encoding

JWTs use Base64URL (not regular Base64):

  • +-
  • /_
  • Padding (=) is removed

This makes JWTs safe for URLs and HTTP headers.

Important: Base64 is encoding, not encryption! Anyone can decode the header and payload:

const payload = JSON.parse(atob(token.split('.')[1]));

Never put sensitive data in JWTs without additional encryption.

How JWT Verification Works

When a server receives a JWT:

  1. Split the token into header, payload, and signature
  2. Decode the header to get the algorithm
  3. Recalculate the signature using the secret key
  4. Compare the calculated signature with the received signature
  5. Check claims (expiration, issuer, etc.)
function verifyJWT(token, secret) {
  const [header, payload, signature] = token.split('.');
  
  const expectedSignature = sign(header + '.' + payload, secret);
  
  if (signature !== expectedSignature) {
    throw new Error('Invalid signature');
  }
  
  const claims = JSON.parse(base64UrlDecode(payload));
  
  if (claims.exp && Date.now() >= claims.exp * 1000) {
    throw new Error('Token expired');
  }
  
  return claims;
}

JWT Claims

Registered Claims (Standard)

Claim Name Description
iss Issuer Who created the token
sub Subject Who the token is about (usually user ID)
aud Audience Who the token is intended for
exp Expiration When the token expires (Unix timestamp)
nbf Not Before Token not valid before this time
iat Issued At When the token was created
jti JWT ID Unique identifier for the token

Public Claims

Custom claims registered with IANA or using collision-resistant names:

{
  "https://example.com/roles": ["admin", "user"]
}

Private Claims

Custom claims agreed upon by parties:

{
  "user_id": "12345",
  "department": "engineering"
}

Common JWT Claims in Practice

{
  "iss": "https://auth.example.com",
  "sub": "user_12345",
  "aud": "https://api.example.com",
  "exp": 1705312800,
  "iat": 1705309200,
  "email": "user@example.com",
  "roles": ["user", "premium"],
  "permissions": ["read:profile", "write:profile"]
}

JWT vs Session Cookies

Feature JWT Session Cookie
Storage Client (localStorage, cookie) Server (database, memory)
Stateless Yes No
Scalability Easier (no session store) Requires shared session store
Revocation Difficult Easy (delete session)
Size Larger (contains data) Small (just session ID)
Cross-domain Works well Cookie restrictions

JWTs shine in:

  • Microservices architecture
  • Mobile apps
  • Single Page Applications (SPAs)
  • Cross-domain authentication

Sessions are better when:

  • You need immediate revocation
  • Token size is a concern
  • You're in a single-domain, traditional web app

Security Considerations

1. Signature Verification is Critical

Never trust a JWT without verifying the signature!

// WRONG - No verification
const payload = JSON.parse(atob(token.split('.')[1]));

// RIGHT - Verify first
const payload = jwt.verify(token, secret);

2. The "alg: none" Vulnerability

Some libraries accept "alg": "none", bypassing signature verification entirely.

// Always specify allowed algorithms
jwt.verify(token, secret, { algorithms: ['HS256'] });

3. Algorithm Confusion Attack

Attacker changes RS256 (asymmetric) to HS256 (symmetric) and signs with the public key (which is public).

// Specify the expected algorithm
jwt.verify(token, publicKey, { algorithms: ['RS256'] });

4. Sensitive Data Exposure

Remember: payloads are encoded, not encrypted!

// NEVER include in JWT:
{
  "password": "secret123",
  "credit_card": "4111111111111111",
  "ssn": "123-45-6789"
}

5. Token Lifetime

Short-lived tokens reduce risk:

  • Access tokens: 15 minutes to 1 hour
  • Refresh tokens: Days to weeks (with rotation)

Token Storage: localStorage vs Cookies

localStorage

localStorage.setItem('token', jwt);
const token = localStorage.getItem('token');

Pros: Easy to use, accessible from JavaScript Cons: Vulnerable to XSS attacks

HTTP-Only Cookies

// Server sets cookie
res.cookie('token', jwt, {
  httpOnly: true,  // Not accessible via JavaScript
  secure: true,    // HTTPS only
  sameSite: 'strict'
});

Pros: Protected from XSS Cons: Vulnerable to CSRF (mitigate with SameSite, CSRF tokens)

Recommendation: Use HTTP-only cookies when possible, with proper CSRF protection.

Refresh Token Pattern

Access tokens are short-lived; refresh tokens get new access tokens:

1. Login → Access Token (15 min) + Refresh Token (7 days)
2. API call with Access Token
3. Access Token expires
4. Exchange Refresh Token for new Access Token
5. Repeat...
6. Refresh Token expires → Re-login required

Benefits:

  • Short-lived access tokens limit damage if stolen
  • Refresh tokens can be revoked server-side
  • Users don't re-login frequently

JWT in Code

Creating a JWT (Node.js)

const jwt = require('jsonwebtoken');

const token = jwt.sign(
  { 
    sub: 'user_12345',
    name: 'John Doe',
    roles: ['user']
  },
  process.env.JWT_SECRET,
  { 
    expiresIn: '1h',
    issuer: 'my-app'
  }
);

Verifying a JWT

try {
  const decoded = jwt.verify(token, process.env.JWT_SECRET, {
    algorithms: ['HS256'],
    issuer: 'my-app'
  });
  // Token is valid, use decoded.sub, decoded.name, etc.
} catch (err) {
  if (err.name === 'TokenExpiredError') {
    // Handle expiration
  } else {
    // Invalid token
  }
}

Summary

JWTs are powerful tools for authentication:

  • Three parts: Header, Payload, Signature (separated by dots)
  • Base64URL encoded: Not encrypted—don't include sensitive data
  • Signature verification: Always verify before trusting
  • Common claims: sub, exp, iat, iss, aud
  • Security: Watch for "alg: none", algorithm confusion, proper storage

Key decisions:

  • Token lifetime (shorter = more secure)
  • Storage method (HTTP-only cookies preferred)
  • Refresh token strategy
  • What claims to include

Need to decode a JWT? Try our JWT Decoder!

Related Topics
jwt json web token authentication authorization security api
Share this article

Continue Reading

Security
Password Security: Hashing, Salting, and Best Practices

How to properly store passwords: why hashing alone isn't enough, what salting does, and modern password security recommendations.

Security
What is HMAC? Message Authentication for Developers

Understanding HMAC (Hash-based Message Authentication Code): how it works, why it's more secure than plain hashes, and implementing it in your APIs.

Security
Understanding Hash Functions: MD5, SHA-1, SHA-256 Explained

A developer's guide to cryptographic hash functions: what they are, how they work, and choosing the right hash for your use case.