JWT Decoder
Paste any JSON Web Token below — decodes header & payload locally, nothing is sent anywhere.
Inside the Token: How JWT Decoding Reveals What Your Auth System Is Actually Sending
A backend engineer at a mid-size fintech company spent three days chasing what looked like a session management bug. Users were being logged out mid-transaction, seemingly at random. The logs showed valid login events, no server errors, and the database sessions looked fine. It wasn't until someone thought to actually decode the JWT tokens flying across the wire that the culprit appeared instantly: the exp claim was set to 900 seconds — 15 minutes — while the product team believed sessions lasted 4 hours. A config line changed in a deploy two weeks earlier had gone unnoticed. The entire investigation collapsed into a two-minute fix once someone decoded the token.
That story repeats itself in different forms across every engineering team that uses JSON Web Tokens. The tokens are opaque-looking strings — three chunks of Base64URL separated by dots — and developers routinely treat them as black boxes, trusting that what was put in is what comes out. Decoding them manually should be a first instinct, not a last resort.
What a JWT Actually Contains
A JSON Web Token is not encrypted by default. It is signed. The distinction matters enormously. When a server issues a JWT, it encodes a JSON header and a JSON payload using Base64URL encoding, concatenates them with a dot, then computes a signature over that string using a secret or private key. The resulting structure — header.payload.signature — can be decoded by anyone who possesses the string. The signature only proves the token hasn't been tampered with; it says nothing about confidentiality.
This means that decoding a JWT requires no secret, no server call, and no special permission. The header and payload are plain Base64URL — a URL-safe variant of Base64 that replaces + with - and / with _, and omits padding characters. Any browser, any command line, any decoder can read them. The only thing that requires the secret is verifying the signature, which is a separate operation entirely.
The header typically contains two fields: alg (the signing algorithm, like HS256 or RS256) and typ (almost always "JWT"). The payload is where the real content lives. The JWT specification defines a set of "registered" claims — standardized field names with specific semantics — and allows applications to add any custom claims they need.
The Registered Claims and Why They Trip People Up
The registered claims are where most debugging sessions live. Understanding each one prevents the class of bugs that the fintech engineer experienced.
iat (Issued At) is a Unix timestamp recording when the token was minted. It is not an expiry. A token issued three years ago with no exp claim is technically still valid from a claims perspective — the application logic must decide whether to accept it.
exp (Expiry) is the claim that matters most for session management. It is a Unix timestamp, and a conforming JWT library will reject the token if the current time is past this value. The common mistake is setting this in the wrong unit — seconds versus milliseconds. A token with exp: 1716242622 expires at a specific UTC moment. A token accidentally set to exp: 1716242622000 (milliseconds) will appear to be valid until the year 56,000, and most libraries will either overflow silently or accept it indefinitely.
nbf (Not Before) is the mirror of exp. A token should not be accepted before this timestamp. This claim is less commonly used but appears in scenarios like pre-issuing tokens for scheduled operations — issuing a token today that becomes valid tomorrow morning.
iss (Issuer) identifies who created the token. In multi-service architectures, this is critical: a token issued by the auth service should not be accepted by the payments service unless the issuer matches what the payments service expects. Skipping issuer validation is a common misconfiguration.
aud (Audience) specifies the intended recipient. A token meant for api.example.com should not be accepted by admin.example.com. Libraries often make audience validation opt-in, meaning it is silently skipped unless explicitly configured.
sub (Subject) identifies the principal — usually a user ID. jti (JWT ID) is a unique identifier for the token itself, used in token revocation schemes where the server maintains a blocklist.
Reading Custom Claims in Practice
Beyond the registered claims, real-world JWTs carry application-specific data. A SaaS platform might include plan, org_id, permissions, and feature_flags directly in the payload. An OAuth token from Google includes email, picture, and email_verified. A Kubernetes service account token includes kubernetes.io/serviceaccount/namespace.
Decoding these tokens during development and debugging is how you confirm that the authorization server is actually encoding what you think it's encoding. A permissions array that shows ["read"] when you expected ["read","write"] explains why the API keeps returning 403. A plan: "free" claim on a user who upgraded yesterday points to a caching bug in the token issuance path.
Security Implications of Inspection
Because JWTs are not encrypted, sensitive data should never live in the payload. Full names, email addresses, and role names are commonly included and are fine — they are semi-public by nature and the signature prevents tampering. But social security numbers, payment card data, internal system paths, or anything that would cause harm if intercepted has no place in a JWT payload.
A decoder tool that runs entirely in the browser addresses one specific concern: not wanting to paste tokens into an external service that might log them. A JWT for a production system carries real session context. Sending it to an online service means that service's servers see your token. Client-side decoding removes that risk entirely — the JavaScript does the Base64URL parsing in your browser's memory, and no bytes leave the machine.
The signature itself is not verifiable without the secret or public key, and that limitation is intentional in a client-side tool. For inspection and debugging purposes, the signature's raw value is all you need to see — it confirms the token is structurally complete. Actual signature verification belongs in your backend, where the secret lives.
Algorithm Confusion: The Attack That Starts at the Header
One notorious JWT vulnerability class — the "algorithm confusion" attack — begins in the header. A token signed with RS256 (asymmetric, using a private key) can sometimes be forged if the verifying library accepts HS256 (symmetric, using a shared secret). An attacker changes the alg header from RS256 to HS256, signs the token with the server's public key as the HMAC secret, and if the library blindly trusts the header's algorithm declaration, verification passes.
Reading the alg claim during debugging is therefore not just informational — it is a security audit step. If a token claims HS256 but your system issues RS256, something is wrong. Mature JWT libraries now reject algorithm downgrade attempts by default, but older codebases and custom implementations may not.
Similarly, the alg: "none" attack — where an attacker strips the signature and declares no algorithm — has burned several high-profile deployments. Spotting "none" in the decoded header of a token your system received is a critical red flag.
Making Decoding a Routine Habit
The engineers who debug JWT issues fastest are the ones who decode first and theorize second. When a user reports they were logged out, decode the token from their request log. When an API returns unexpected permissions, decode the token in the Authorization header. When a new auth provider integration behaves strangely, decode the tokens it issues before touching any code.
The information is all there, readable without any server access, any database query, or any special tooling — just Base64URL decoding and JSON parsing. The three-part structure of a JWT is deliberately human-inspectable. Using that property actively, rather than treating the token as opaque infrastructure, is what separates confident JWT debugging from hours of guesswork.