The Shack Security JWT Decoded: Understanding JSON Web Tokens

JWT Decoded: Understanding JSON Web Tokens Without the Jargon

Back to All Posts

If you've built anything with a modern API, OAuth login, or a single-page app in the last several years, you've used JWTs — JSON Web Tokens. They show up in Authorization headers, cookies, localStorage, and URL parameters. But a surprising number of developers treat them as magic strings without understanding what's actually inside — which leads to some serious security mistakes.

Paste any JWT into the DevToolShack JWT Decoder and you'll see exactly what's inside instantly. This article explains what you're looking at and why it matters.

What Is a JWT?

A JSON Web Token is a compact, self-contained way to transmit information between parties as a JSON object. The key word is self-contained — the token carries its own claims about who the user is and what they're allowed to do, without requiring a database lookup on every request.

A JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkYSBMb3ZlbGFjZSIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Three Base64url-encoded sections separated by dots. Each section has a specific purpose.

The Three Parts

Part 1: Header

The first section decodes to a JSON object describing the token type and signing algorithm:

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

alg tells the receiver which algorithm was used to sign the token. Common values: HS256 (HMAC-SHA256, symmetric), RS256 (RSA-SHA256, asymmetric), ES256 (ECDSA, asymmetric).

Part 2: Payload (Claims)

The second section is the actual data — a set of "claims" about the user or session:

{
  "sub": "1234567890",
  "name": "Ada Lovelace",
  "email": "ada@example.com",
  "role": "admin",
  "iat": 1516239022,
  "exp": 1516242622
}

Standard claim names:

ClaimNameMeaning
subSubjectWho the token is about (usually user ID)
issIssuerWho created the token
audAudienceWho the token is intended for
expExpirationUnix timestamp when the token expires
iatIssued AtUnix timestamp when the token was created
nbfNot BeforeToken is invalid before this timestamp
jtiJWT IDUnique identifier for this token

Part 3: Signature

The third section is a cryptographic signature over the header and payload. For HS256, it's computed as:

HMAC-SHA256(
  base64url(header) + "." + base64url(payload),
  secret_key
)

The signature lets the receiver verify that the token hasn't been tampered with. If anyone modifies the payload (say, changing "role": "user" to "role": "admin"), the signature becomes invalid.

The Critical Distinction: Signed, Not Encrypted

JWTs are not encrypted by default. The header and payload are Base64url-encoded — which anyone can decode in seconds. Never put sensitive information (passwords, payment details, SSNs) in a JWT payload unless you're using JWE (JSON Web Encryption), which is a separate standard.

The signature proves the token is authentic and unmodified — it doesn't hide the contents. Think of it like a sealed envelope with a wax seal: the seal proves it hasn't been opened, but the contents are readable once you open it.

How Verification Works

When your server receives a JWT:

  1. Split the token on the dots — header, payload, signature
  2. Recompute the expected signature using the header + payload + your secret key
  3. Compare the computed signature to the received signature
  4. If they match, the token is valid and untampered
  5. Check that exp hasn't passed
  6. Optionally check iss and aud

This is why JWTs are "stateless" — your server doesn't need to look up anything in a database. All the information needed to validate the token is in the token itself plus your secret key.

Common Security Mistakes

The "alg: none" Attack

Some early JWT libraries accepted "alg": "none" in the header, treating tokens with no signature as valid. Always verify that your library explicitly rejects alg: none.

Storing JWTs in localStorage

localStorage is accessible to any JavaScript on the page — including injected scripts from XSS attacks. For sensitive apps, store JWTs in HttpOnly cookies, which are inaccessible to JavaScript.

Not Validating Expiry

Always check the exp claim server-side. Don't rely on the client to discard expired tokens.

Weak Secrets with HS256

HS256 uses a shared secret. If that secret is short or guessable, attackers can brute-force valid signatures. Use a long random secret (256+ bits) or switch to RS256 with a proper keypair.

Debugging tip: When an auth flow breaks and you're not sure what's in the token, paste it into the JWT Decoder. It instantly shows you the decoded header and payload — including the expiry timestamp converted to a human-readable date. Saves a lot of head-scratching.

HS256 vs RS256: Which to Use?

FeatureHS256 (Symmetric)RS256 (Asymmetric)
Key typeSingle shared secretPublic/private keypair
Who can signAnyone with the secretOnly the private key holder
Who can verifyAnyone with the secretAnyone with the public key
Best forSingle service, simple setupMicroservices, third-party consumers
Secret exposure riskHigher — secret shared with verifiersLower — public key is safe to share

For microservice architectures where multiple services verify tokens but only one issues them, RS256 is the stronger choice — verifying services only need the public key, not the signing secret.