Decode and debug JWTs instantly. Learn how JSON Web Tokens work, what the header/payload/signature mean, and how to validate tokens securely in 2026.
A JWT that you cannot verify is just base64-encoded data with a false sense of security — and that describes the majority of JWT implementations in production today.
JSON Web Tokens (JWTs) appear in auth headers, cookies, and query parameters across virtually every modern web application. They carry user identity, authorization claims, and session state. They are passed between services, stored in browsers, and sent to third-party APIs. Despite their ubiquity, JWT security is frequently misunderstood — and the misunderstandings are exploitable.
This guide explains exactly how JWTs work, what each component contains, how signatures are validated, and where implementations go wrong. Use the free WOWHOW JWT decoder to inspect any token as you read.
JWT Structure: The Three Parts
A JWT looks like this:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNzE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Three base64url-encoded segments separated by dots: header, payload, signature. Each is independently decodable. None of them are encrypted (unless you use JWE — JSON Web Encryption — which is a separate spec).
The Header
The header declares the token type and the signing algorithm:
{
"alg": "RS256",
"typ": "JWT"
}
The alg field is critical from a security standpoint. Common values:
- HS256: HMAC-SHA256 — symmetric, same key signs and verifies
- RS256: RSA-SHA256 — asymmetric, private key signs, public key verifies
- ES256: ECDSA-SHA256 — asymmetric, smaller signatures than RSA
- none: No signature — never accept this in production
The alg: "none" attack is a well-documented vulnerability: an attacker crafts a token with "alg": "none" and no signature. Naive libraries accept it because the spec technically allows unsigned tokens. Any production JWT library must explicitly disable alg: "none".
The Payload
The payload contains claims — statements about the subject (usually a user) and metadata. The JWT spec defines standard claim names:
- sub: Subject — the user ID or entity the token represents
- iss: Issuer — the server that created the token
- aud: Audience — the service(s) that should accept this token
- exp: Expiration time — Unix timestamp after which the token is invalid
- iat: Issued at — when the token was created
- jti: JWT ID — unique identifier for the token (used to prevent replay)
- nbf: Not before — the token is invalid before this timestamp
Custom claims carry application-specific data — roles, permissions, tenant IDs. A typical application payload might include role: "admin" or plan: "pro". These are claims your server made at token issuance time.
Critical: The payload is not encrypted. Anyone who holds the token can decode and read the payload. Never put sensitive data (passwords, payment details, PII beyond what's necessary for auth) in a JWT payload.
The Signature
The signature is computed over the concatenation of the encoded header and encoded payload:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
For RSA (RS256), the private key signs this concatenation. The receiving server verifies using the public key. If the signature does not match, the token was tampered with — either the payload claims were modified, or the token was forged.
Signature verification is not optional. It is the entire security model. A server that decodes a JWT payload without verifying the signature is trusting user-controlled input. The JWT decoder shows you the decoded header and payload instantly — always verify signatures in your application code, not just decode.
JWT Validation: The Complete Checklist
Decoding a JWT is trivial (base64url decode). Validating it is where most implementations fall short. A complete validation requires checking all of the following:
1. Algorithm Verification
Verify that alg matches your expected algorithm. Never accept alg: "none". If you expect RS256, reject HS256 — an attacker can take your public key, sign a token with it using HS256 (treating the public key as the HMAC secret), and a naive library that accepts both will verify it as valid.
2. Signature Verification
Verify the signature using the correct key for the algorithm. For RS256 and ES256, fetch the public key from your identity provider's JWKS endpoint (/.well-known/jwks.json). Cache the JWKS with a reasonable TTL — fetching it on every request is a performance problem and a potential DoS vector.
3. Expiration (exp)
Reject tokens where exp is in the past. Allow a small clock skew (1–2 minutes) for distributed systems where clocks are not perfectly synchronized. Never accept a token without an exp claim unless you have specific requirements for non-expiring tokens.
4. Audience (aud)
Verify that aud matches your service identifier. An access token issued for api.example.com should be rejected by payments.example.com. Skipping audience validation enables token substitution attacks where a token from one service is replayed against another.
5. Issuer (iss)
Verify that iss matches your expected token issuer. If you use multiple identity providers, ensure each service validates against the correct issuer.
Comments · 0
No comments yet. Be the first to share your thoughts.