How to Decode a JWT

A JSON Web Token packs claims into three base64url-encoded parts. Here's how to decode each one, read the common claims, and stay safe — because decoding is never the same as verifying.

What a JWT actually is

A JWT (JSON Web Token, pronounced "jot") is a compact, URL-safe string used to carry claims between two parties — typically an authentication server and an API. It is made up of three parts separated by dots:

header.payload.signature

Each part before the dots is base64url-encoded JSON (base64url is the URL-safe variant of base64: + becomes -, / becomes _, and trailing = padding is dropped). A real token looks like one long opaque string:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDI2MjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Decoding simply reverses the base64url encoding so you can read the JSON inside. It does not require any secret or key — anyone can do it.

How to decode each part

To decode a JWT by hand, split it on the two dots and base64url-decode the first two segments:

  • Header — the first segment. Describes the token type and the signing algorithm, e.g. alg and typ.
  • Payload — the second segment. Holds the claims: the actual data about the subject and the token.
  • Signature — the third segment. This is not JSON and is not meant to be read. It is a cryptographic value used to verify the first two parts.

The fastest way is to paste the whole token into Formatly's JWT Decoder. It splits the token, base64url-decodes the header and payload, and pretty-prints the JSON for you — all locally in your browser. The token is never uploaded or sent anywhere.

A worked example

Take the token above and decode its first two segments. The header base64url-decodes to:

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

This tells you the token was signed with HMAC-SHA256. The payload decodes to:

{
  "sub": "1234567890",
  "name": "Jane Doe",
  "iat": 1516239022,
  "exp": 1516242622
}

Now the claims are human-readable: a subject ID, a name, an issued-at time, and an expiry. The signature segment stays encoded — it only matters during verification.

Common claims and reading expiry

The JWT spec defines a set of registered claims. The ones you'll see most often are:

  • sub (subject) — who the token is about, usually a user ID.
  • iss (issuer) — who created and signed the token.
  • aud (audience) — the intended recipient(s); your API should reject tokens whose aud isn't you.
  • iat (issued at) — when the token was created.
  • exp (expiration) — after this time the token must be rejected.
  • nbf (not before) — the token is invalid until this time.

The time-based claims (iat, exp, nbf) are NumericDate values — seconds since the Unix epoch (1970-01-01 UTC), not milliseconds. To check expiry, compare exp against the current epoch time. In the example, exp is 1516242622, exactly one hour after iat (1516239022), so the token had a one-hour lifetime. Formatly's decoder surfaces these timestamps so you can see at a glance whether a token is expired.

Decoding is NOT verifying

This is the single most important thing to understand. Decoding only reads the data; it proves nothing. Anyone can take a token, change the payload, re-encode it, and produce a string that decodes perfectly — but the signature won't match.

  • Never trust an unverified token. The decoded payload is just claims someone says are true. Your server must verify before acting on them.
  • The signature proves integrity. Verification recomputes the signature over the header and payload using the secret (for HMAC like HS256) or the issuer's public key (for RSA/ECDSA like RS256). If it matches, the token wasn't tampered with and came from someone holding the key.
  • A decoder like Formatly's does not — and cannot, without the key — tell you a token is genuine. It just shows you what's inside.

Signed, not encrypted

A standard JWT is signed, not encrypted. The payload is only base64url-encoded, which is trivially reversible — exactly what we did above. That means:

  • Never put secrets in the payload. Passwords, API keys, private personal data, or anything confidential is effectively public to anyone who holds the token.
  • Treat the payload as readable by anyone who can intercept the token. If you genuinely need confidentiality, that's a separate standard (JWE), not a plain JWT.

Because tokens often live in logs, browser storage, and request headers, assume the contents will be seen.

Decode safely with Formatly

Pasting a token into a random online decoder is risky — many real tokens are live credentials. Formatly's JWT Decoder runs entirely in your browser. Nothing is uploaded, logged, or transmitted; the decoding happens on your machine, so even a production token stays private. Paste your token, read the header and payload, check exp — then close the tab and it's gone.

FAQ

Do I need the secret key to decode a JWT?

No. Decoding a JWT only reverses the base64url encoding of the header and payload, which requires no key at all — anyone can read the claims. You only need the secret or public key to verify the signature, which proves the token wasn't tampered with. Formatly's JWT Decoder shows you the decoded contents without any key, entirely in your browser.

Is decoding a JWT the same as verifying it?

No, and conflating the two is a serious security mistake. Decoding just reads the data inside; verifying recomputes the signature with the secret (HMAC) or public key (RSA/ECDSA) to confirm the token is authentic and unmodified. A decoded payload is only a set of claims someone asserts — never trust it until your server has verified the signature.

Is the data in a JWT encrypted?

No. A standard JWT is signed, not encrypted. The payload is merely base64url-encoded, so anyone holding the token can decode and read it in seconds. Never store passwords, API keys, or confidential personal data in the payload. If you truly need the contents hidden, you need JWE (JSON Web Encryption), which is a different standard.

How do I tell if a JWT is expired?

Look at the exp claim in the decoded payload. It's a NumericDate — seconds since the Unix epoch (not milliseconds). If exp is less than the current epoch time, the token has expired and should be rejected. Also check nbf (not before), since a token isn't valid until that time. Formatly's decoder surfaces these timestamps so you can read expiry at a glance.

Is it safe to paste a real token into an online JWT decoder?

It depends entirely on where the decoding happens. Many tokens are live credentials, so pasting them into a tool that uploads them to a server is risky. Formatly's JWT Decoder decodes locally in your browser — the token is never sent, stored, or logged anywhere — so even a production token stays private on your machine.