How to Decode a JWT Token Before You Trust It

A JWT looks like security. It is three chunks of text with dots. People paste them into dashboards, stash them in localStorage, and assume the payload is gospel because it arrived in a token.

A JWT is a signed statement, or sometimes an encrypted one. Decoding the middle part is reading the statement. Verifying the signature is deciding whether to believe it. Those are different steps. A decoder on Nicxro helps you read. Your server still has to verify.

This guide is how to decode a JWT safely, what the fields mean, and which mistakes turn a token into an incident.

What a JWT is made of

Three parts, separated by .:

Header: JSON, Base64url-encoded. Usually the algorithm and token type.

Payload: JSON, Base64url-encoded. Claims: subject, expiry, roles, whatever the issuer put there.

Signature: binary, Base64url-encoded. Proves the header and payload were signed with a key the issuer holds, if you verify it.

If you Base64-decode the payload, you can read claims without the secret. That is by design. JWTs are not a hiding place. Anyone who has the token can read the payload unless it is an encrypted JWE, which is less common in simple apps.

Never put a password, a full card number, or a government ID in a JWT payload. Treat it like a postcard the user can open.

How to decode without panicking

Paste the full token into Nicxro’s JWT decoder. You should see header JSON and payload JSON. If the tool fails, check:

  • You pasted with Bearer still attached. Strip the word Bearer and the space.
  • Extra quotes or newlines from a log.
  • A JWE with five parts. That is encrypted. A simple decoder for JWS will not show a useful payload.
  • Truncation in logs. Tokens are long. Logging systems love to cut them.

Formatted JSON from the payload is the point. Look at exp, iat, nbf, iss, aud, sub. Unix timestamps look like 1735689600. Convert them. A token that “should work” is often expired by an hour because someone’s clock or timezone code is wrong.

Reading claims like an engineer, not like a tourist

sub is who the token is about. If it is a user id, your API should not trust a different id in the URL without a check.

iss is who minted it. Your API should reject issuers it does not know.

aud is who it is for. A token minted for api-admin should not work on api-public if you configured audiences.

exp is expiry. Leeway of a minute can help with clock skew. Leeway of a day is how stolen tokens live forever.

Custom claims like role: admin are only meaningful if the signature is valid and the issuer is yours. A decoded payload that says admin is not proof. An attacker can make a token with that payload and a junk signature. If your library skips verification, they are admin.

That is the classic alg: none bug. Old libraries accepted unsigned tokens. Do not write your own JWT verify. Use a maintained library. Disable none. Pin the algorithms you allow. If you mint with RS256, do not accept HS256 with your public key as a HMAC secret. That mix-up has been a real class of bugs.

Decode is for debugging. Verify is for production.

Use the decoder when:

  • A user is “randomly” logged out. Check exp.
  • A role is missing. Check the claims your auth server was supposed to add.
  • Two environments fight. Compare iss and aud on staging vs production tokens.
  • A mobile app sends a token that looks wrong. See if it is an access token or an id token.

Do not use the decoder as your API’s authentication. The browser tool will not know your public keys. Even if a tool offered “verify” and you pasted a secret, you would be pasting a secret into a website. For HMAC secrets, that is a bad habit. Verify on the server with keys from a vault.

If the token is a production user token, paste with care. It may be enough to impersonate that user until expiry. Decode on a machine you trust. Do not dump live tokens into a public ticket. Redact sub if the ticket is widely visible, or use a staging token.

localStorage vs cookies, briefly

If you decode a token from localStorage, XSS can steal it. If it is in an httpOnly cookie, JavaScript cannot read it, which is good, and CSRF becomes the thing you must handle.

The decoder does not choose your storage. Seeing a JWT in DevTools Application tab should remind you that anything JS can read, a script injection can read. Keep tokens short-lived. Use refresh tokens with rotation if you need sessions that last.

When the payload looks like nonsense

You decoded with standard Base64 instead of Base64url. Use a JWT-aware tool.

The payload is compressed (zip in the header). Rare, but some issuers do it.

You are looking at the signature part and expecting JSON.

The token is opaque, not a JWT. Some “bearer tokens” are random strings. They will not have two dots. Do not force them through a JWT decoder.

Clock and timezone bugs

exp is UTC epoch. Your UI might print local time. A token that expires at 00:00 UTC expires in the previous local evening in the US. People file bugs that “the token lasted one day less.” Decode, convert epoch with an explicit UTC conversion, then argue.

nbf in the future means the token is not valid yet. A server with a clock set two hours fast will reject fresh tokens. Decode shows nbf. NTP on the server fixes it.

A debug checklist

  1. Copy the token only, no Bearer prefix.
  2. Decode on Nicxro.
  3. Confirm iss, aud, exp.
  4. Confirm the claims your app reads.
  5. On the server, verify signature with the right key and algorithm.
  6. Only then change application code.

If you skip verification in development with a flag, put the flag in a place you cannot ship. A TODO: skip jwt verify has shipped before.

What not to put in the token

Keep payloads small. They are sent on every request if you use them as bearer tokens. Giant permission documents belong on the server.

Do not put PII you would not want the user or a CDN log to see. Email in a JWT is common and still worth a think on public logs.

Do not treat a JWT as a session database. If you must revoke access immediately, you need a blocklist, short expiry, or a server-side session. Decoding an unexpired token will still look “valid” after you fired the employee, until you check revocation.

Access tokens, ID tokens, and refresh tokens

OAuth and OpenID Connect issue more than one kind of string. People paste the wrong one into an API and then decode it looking for a role that lives on a different token.

An access token is what you send to an API as Authorization: Bearer. It may be a JWT or it may be opaque. If it is a JWT, the audience should be the API, not the frontend.

An ID token is for the client to know who logged in. It is a JWT by spec. APIs should not accept ID tokens as access tokens unless you designed that on purpose, which most people should not.

A refresh token should be stored carefully and often is not a JWT you decode in a blog tab. If you find a refresh token in frontend JavaScript, treat that as an architecture review, not a decoder exercise.

When a request fails with 401, decode the access token you actually sent. You may find you sent the ID token, an expired access token, or a token from the other environment’s issuer. That is a five-minute fix once you can see the claims.

Kid, JWKS, and “invalid signature” that is really key rotation

The header often has kid, a key id. Your server should load the issuer’s JWKS, pick the key that matches kid, and verify. If the identity provider rotated keys and your app cached JWKS for a day, valid tokens fail. Decoding still looks fine. The payload is readable. The signature check is what fails.

When Nicxro shows a healthy payload and your API says invalid signature, do not rewrite claims. Check algorithm, issuer URL, audience, clock, and JWKS. Those are the usual four. A fifth is using the HS256 shared secret from a sample tutorial in production. Rotate that.

The habit

When a JWT misbehaves, read it. Nicxro will show the JSON. Believe the timestamps. Be suspicious of the claims until the signature is verified in your own code. A token is a letter. Decoding is opening the envelope. The seal is the signature. Do not skip the seal because the letterhead looks official.

Leave a Comment