JWT (JSON Web Token)

Authentication Security Notes Jan 6, 2025 JAVASCRIPT
authentication security token stateless authorization

Definition

Remember those wristbands you get at amusement parks or music festivals? The ones with a special pattern or chip that security can scan to verify you paid for admission, what type of ticket you have, and maybe even what extra areas you can access? A JWT (pronounced “jot”) works exactly the same way for web applications - it’s a compact, self-contained pass that carries information about who you are and what you’re allowed to do.

JWT stands for JSON Web Token, and it’s one of the most widely used ways to handle authentication on the internet today. When you log into a website, instead of the server keeping track of your session in a database (which gets complicated when you have millions of users), it gives you a JWT - a special string of characters that contains your identity information, digitally signed so no one can tamper with it. Every time you make a request to that website, you send this token along, and the server can verify it’s legitimate without looking anything up.

A JWT has three parts separated by dots: a header (which describes how the token is secured), a payload (which contains the actual information about you - called “claims”), and a signature (which proves the token hasn’t been modified). The clever thing is that anyone can read the information in a JWT (it’s just encoded, not encrypted), but only someone with the secret key can create a valid signature. So if someone tries to change the payload to give themselves admin privileges, the signature won’t match, and the server will reject it.

Example

Real-World Scenario 1: Single Sign-On (SSO) When you click “Login with Google” on a website like Canva, Google verifies your identity and gives Canva a JWT. This token contains your email, name, and a verified statement that “Google confirms this person is who they say they are.” Canva trusts Google’s signature, so they let you in without ever seeing your Google password. The JWT travels with every request you make to Canva.

Real-World Scenario 2: Mobile App Authentication When you log into Spotify on your phone, you receive a JWT that your app stores locally. For the next few hours, every time you play a song, create a playlist, or browse recommendations, your app sends this JWT with each request. Spotify’s servers verify the token instantly without checking a central database - that’s how millions of people can stream simultaneously.

Real-World Scenario 3: Microservices Communication In Netflix’s architecture, when the “recommendation service” needs to fetch your watch history from the “viewing history service,” it passes along your JWT. The viewing history service can verify you’re authorized to see this data by checking the token’s signature, without calling back to a central auth server. This makes the whole system faster and more scalable.

Real-World Scenario 4: API Rate Limiting When you use a weather API like OpenWeatherMap, your JWT might contain claims about your subscription tier. The API checks these claims to determine whether you can make 60 requests per minute (free tier) or 3000 requests per minute (professional tier). All this information travels in the token itself - no database lookup needed.

Analogy

The Concert Wristband: When you enter a concert, you get a wristband. It might be a specific color (VIP vs general admission), have a holographic pattern (to prevent counterfeiting), and maybe a chip that encodes your ticket information. Security can quickly verify your access level by looking at the wristband without checking a guest list. JWTs work the same way - they carry your access information, are tamper-proof through signatures, and can be verified quickly.

The Notarized Document: Imagine a document that states who you are and what you’re authorized to do, signed by a notary public. Anyone can read the document, but the notary’s stamp proves it’s legitimate. If someone changes the document, the stamp is invalid. A JWT is like a digital notarized document - readable by anyone, but the signature proves authenticity.

The Boarding Pass: Your airline boarding pass contains your name, flight number, seat, and boarding group. TSA scans it to verify it’s legitimate (checking the barcode signature) without calling the airline. The information is right there on the pass, encoded in a format that can’t be faked. JWTs are digital boarding passes for web applications.

The Employee Badge: In a large company, your ID badge might have your photo, name, department, and clearance level printed on it, plus a chip that door readers can scan. Different doors grant access based on reading your badge - no need to check a central computer for each door. JWTs serve the same purpose in digital systems.

Code Example


// Create JWT
const jwt = require('jsonwebtoken');

const payload = {
  sub: 'user123',           // Subject (user ID)
  name: 'John Doe',
  role: 'admin',
  iat: Math.floor(Date.now() / 1000),  // Issued at
  exp: Math.floor(Date.now() / 1000) + (60 * 60)  // Expires in 1 hour
};

const secret = process.env.JWT_SECRET;
const token = jwt.sign(payload, secret, { algorithm: 'HS256' });

// Verify JWT
try {
  const decoded = jwt.verify(token, secret, {
    algorithms: ['HS256'],
    maxAge: '1h'
  });
  console.log('Valid token:', decoded);
} catch (err) {
  console.error('Invalid token:', err.message);
}

// Use with API
fetch('https://api.example.com/data', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

Diagram

flowchart TB
    subgraph Structure["JWT Structure: header.payload.signature"]
        direction LR
        Header["eyJhbGci...
(Header)"] Dot1["."] Payload["eyJzdWIi...
(Payload)"] Dot2["."] Sig["SflKxwRJ...
(Signature)"] Header --- Dot1 --- Payload --- Dot2 --- Sig end subgraph Decoded["Decoded Content"] direction TB H["Header (JSON)
{
alg: RS256,
typ: JWT
}"] P["Payload (JSON)
{
sub: user123,
name: John,
exp: 1699999999,
iat: 1699996399
}"] S["Signature
RSASHA256(
base64(header) +
base64(payload),
secret
)"] end Header --> H Payload --> P Sig --> S subgraph Validation["Validation Flow"] V1["1. Decode header & payload"] V2["2. Verify signature with secret/public key"] V3["3. Check exp, iat, nbf claims"] V4["4. Verify iss and aud"] V1 --> V2 --> V3 --> V4 end

Security Notes

SECURITY NOTES

CRITICAL: JWT is stateless but still requires proper validation. Never trust claims without signature verification.

Signature Validation:

  • Always validate signature first: Check signature before trusting any claim
  • Specify algorithms: Whitelist allowed algorithms; never allow “none”
  • Asymmetric preferred: Use RS256 (RSA) or ES256 (ECDSA) over HS256 (HMAC)
  • Key management: Securely store and rotate signing keys
  • Public key caching: Cache public keys with reasonable TTL

Token Expiration:

  • Check exp claim: Validate token hasn’t expired
  • Reasonable lifetime: Keep token lifetime short (15 minutes to 1 hour)
  • Clock skew: Allow small clock skew (30-60 seconds) in validation
  • nbf and iat: Validate “not before” and “issued at” claims
  • Custom timestamps: Use current time, not time from claim

Claim Validation:

  • Validate iss (issuer): Verify token came from trusted issuer
  • Validate aud (audience): Verify token intended for your application
  • Validate jti (JWT ID): Implement token revocation using jti
  • Validate custom claims: Type-check all claims (roles as array, not string)

Token Leakage Prevention:

  • HTTPS only: Always transmit JWTs over HTTPS
  • Bearer tokens: Use in Authorization header, not URL or cookies
  • Secure storage: Store in secure httpOnly cookies or memory
  • Minimize claims: Don’t store sensitive data in JWT (it’s only BASE64 encoded)
  • PII exclusion: Never include passwords, credit cards, SSNs

Revocation & Blacklisting:

  • No built-in revocation: JWTs cannot be revoked without server-side store
  • Implement blacklist: Maintain token blacklist for revoked tokens
  • Short lifetimes: Use short expiration to limit revocation period
  • jti claim: Use JWT ID claim for revocation tracking

Common Vulnerabilities:

  • Algorithm confusion: Attacker changes alg to HS256 (symmetric)
  • None algorithm: Algorithm set to “none” bypasses signature checking
  • Key confusion: Using wrong key type for algorithm
  • Claim injection: Attacker modifies payload if signature not checked

Best Practices

  1. Use strong signing algorithms (RS256 for asymmetric, HS256 with strong secret)
  2. Set short expiration times (5-60 minutes) and use refresh tokens
  3. Store tokens securely (httpOnly cookies for web, secure storage for mobile)
  4. Implement token refresh mechanism with rotation
  5. Never store sensitive data in payload (it is only base64 encoded, not encrypted)
  6. Validate all claims on the server (exp, iss, aud, nbf)

Common Mistakes

Using “none” algorithm (security vulnerability), storing JWTs in localStorage without XSS protection, not validating signature on server side, including sensitive data in payload, forgetting to check exp claim, using weak signing secrets, not implementing token refresh, trusting client-side token validation.

Standards & RFCs