Security & Cryptography

Understanding JSON Web Tokens (JWT): Structure, Security Flaws & Best Practices

JSON Web Tokens (RFC 7519) are the standard mechanism for stateless authentication in modern distributed microservices and single-page applications. However, subtle implementation mistakes can render authentication systems completely vulnerable to account takeover.

Saud Siraj• Published Sep 2026• 9 min read

Need to inspect and decode a JWT token?

Decode headers, claims, and expiry dates 100% locally in your browser without sending tokens to any server.

Open JWT Decoder

1. The Three Parts of a JWT (JWS Compact Serialization)

A JSON Web Token is a compact string consisting of three distinct segments separated by periods (.):

header.payload.signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTc4OTY1ODAwMH0.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Part 1 (Red) : Header (Algorithm & Token Type)
Part 2 (Purple): Payload (User Identity & Claims)
Part 3 (Blue) : Cryptographic Signature

2. Critical Distinction: Signed vs. Encrypted

A standard JWT is a JSON Web Signature (JWS), not a JSON Web Encryption (JWE).

The payload is NOT encrypted. It is simply Base64URL encoded. Anyone who intercepts the token can paste it into our JWT Decoder and read every claim, email address, role, and permission. Never place secret passwords, private keys, or unencrypted personally identifiable information (PII) inside a JWT payload.

3. The Infamous "alg: none" Attack

In early JWT library implementations, the specification allowed an algorithm value of "none" for unsigned tokens. Malicious actors exploited this by taking a legitimate token, changing the payload claim from "admin": false to "admin": true, setting "alg": "none" in the header, and stripping the signature entirely.

Vulnerable verification libraries would read the header, see "none", skip cryptographic signature verification, and grant the attacker full administrative access!

// ✅ Production rule: Explicitly lock down accepted verification algorithms
jwt.verify(token, process.env.JWT_SECRET, {
  algorithms: ['HS256'] // Reject 'none', 'RS256', and unexpected algorithms!
});

4. The Algorithm Confusion Attack (RSA to HMAC)

When an application uses asymmetric encryption (e.g., RS256), the server signs the token using its private key, and any service verifies it using the public key.

If the backend verification code is written carelessly:

// ❌ Vulnerable to algorithm substitution:
jwt.verify(token, serverPublicKey);

An attacker can download the server's public key (which is public!), change the token header from RS256 to HS256 (symmetric HMAC), sign the forged token using the public key as the HMAC secret, and send it to the server. The server verifies the HMAC signature using its own public key and accepts the forged token!

5. Token Storage: LocalStorage vs. HttpOnly Cookies

Where should you store JWTs in single-page applications?

  • LocalStorage: Vulnerable to Cross-Site Scripting (XSS). Any compromised third-party npm package, CDN script, or injected script can read localStorage.getItem('token') and exfiltrate your session token.
  • HttpOnly, Secure, SameSite Cookies: Immune to JavaScript inspection. JavaScript in the browser cannot read HttpOnly cookies. To prevent Cross-Site Request Forgery (CSRF), set SameSite=Lax or SameSite=Strict.

6. Short Expirations & Refresh Token Rotation

Because JWTs are stateless, you cannot easily revoke a token before its expiration (exp) without introducing a stateful database blacklist.

The industry gold standard:

  1. Issue a short-lived Access Token (valid for 5 to 15 minutes) kept in memory or cookie.
  2. Issue a long-lived Refresh Token (valid for 7 to 30 days) stored in an HttpOnly cookie with database rotation.
  3. When refreshing tokens, invalidate the old refresh token immediately. If a previously consumed refresh token is ever submitted, immediately revoke the entire user session (detecting token theft).