Understanding JWT: Structure, Security, and Common Pitfalls

How JSON Web Tokens are structured, where their security comes from, and the mistakes that leave authentication systems vulnerable.

By DevToolsBox · January 25, 2026

JSON Web Tokens (JWTs) are the workhorse of modern stateless authentication. They let a server hand a client a self-contained token that proves who the user is, without looking up a session on every request. But JWTs are also the source of many security mistakes — almost always because developers misunderstand what a JWT actually is. This article breaks down the structure, the security model, and the pitfalls to avoid.

The three parts of a JWT

A JWT is a string with three Base64URL-encoded segments separated by dots:

header.payload.signature
  • Header — describes the token type and the signing algorithm, e.g. {"alg":"HS256","typ":"JWT"}.
  • Payload — contains the claims: statements about the subject. Standard claims include sub (subject/user ID), iat (issued-at), exp (expiration), iss (issuer), and aud (audience). You can also add custom claims like roles or scopes.
  • Signature — produced by signing the encoded header and payload with a secret (for HMAC) or a private key (for RSA/ECDSA). The signature lets the server verify the token has not been tampered with.

You can decode the header and payload yourself with the JWT Decoder — it simply reverses the Base64URL encoding and pretty-prints the JSON.

The single most important fact about JWTs

A JWT is encoded, not encrypted. Anyone who obtains the token can read every claim in the payload. The Base64URL encoding is just a transport format; it provides no confidentiality. The signature protects integrity (the token cannot be changed without invalidating the signature), not secrecy.

This has immediate consequences:

  • Never put secrets in the payload. Passwords, API keys, and personal data must not travel in a JWT unless the token is also encrypted (JWE).
  • Always transport tokens over HTTPS. Without TLS, an attacker on the network can read and steal the token.
  • Treat access tokens as bearer credentials. Whoever holds the token can act as the user until it expires.

Where the security comes from

The security of a JWT rests entirely on the signature and the secret or key behind it. When the server receives a token, it re-computes the signature over the header and payload and compares it to the one in the token. If they match, the payload is trusted.

Common signing algorithms:

  • HS256 (HMAC with SHA-256) — symmetric. The same secret signs and verifies. Simple, but the secret must be shared with every service that verifies tokens.
  • RS256 (RSA signature with SHA-256) — asymmetric. A private key signs; a public key verifies. Ideal when many services need to verify tokens without being able to mint them.
  • ES256 (ECDSA) — asymmetric, like RS256, but with smaller keys and signatures.

The signing algorithm is declared in the header — which is itself part of the token and therefore attacker-controllable. This leads to the most famous JWT vulnerability.

The alg: none attack

Early JWT libraries trusted the alg field in the header. An attacker could take a valid token, change the header to {"alg":"none"}, modify the payload (say, elevate their role to admin), drop the signature, and submit it. A vulnerable library would see alg: none, skip verification, and trust the payload.

The fix is straightforward: always verify the signature, and pin the expected algorithm on the server. Never accept alg: none, and never let the token dictate which algorithm the verifier uses. Modern libraries make this the default, but it is worth confirming.

Common pitfalls

  • Storing JWTs in localStorage. This exposes them to any JavaScript running on the page, including cross-site scripting (XSS) payloads. Prefer an HttpOnly, Secure, SameSite cookie for browser-based apps.
  • Long-lived access tokens. If a token is stolen, it is valid until it expires. Keep access tokens short-lived (minutes, not hours) and use a refresh token for obtaining new ones.
  • No revocation. Stateless JWTs cannot be individually revoked without a server-side denylist. If you need logout-everywhere or immediate revocation, you need a stateful layer.
  • Ignoring exp. Always check the exp (and nbf) claims on the server. A token without an expiration is a liability.
  • Mistaking the signature for encryption. Remember: the payload is readable by anyone. If you decode a token with the JWT Decoder, you will see every claim in cleartext.

A safe mental model

Treat a JWT as a signed, readable assertion that the server issued. The signature proves authenticity; the exp claim bounds its lifetime; HTTPS protects it in transit; and a secure storage mechanism protects it at rest in the browser. Get those four things right and you will have avoided the vast majority of JWT security issues.

For day-to-day debugging, keep a JWT Decoder handy — but remember that it only decodes. Signature verification must always happen on your server, with the secret or public key you control.

Related Tools