Encoders & Decoders

JWT Decoder & Encoder

Decode a JSON Web Token to read its header, payload, and expiry, or sign a new HS256 token. Everything runs in your browser.

Paste a JSON Web Token

Decoding happens in this tab. The token is never uploaded, logged, or sent anywhere, which matters because a real JWT is a live credential: anyone holding it can act as you until it expires.

What is a JWT decoder?

A JSON Web Token is three chunks of base64url text joined by dots. Header, payload, signature. A decoder splits the token on those dots and turns the first two chunks back into readable JSON. That is the whole job, and usually enough, because the part you came to read is the payload: which user the token names, which roles it carries, and when it stops being accepted.

Paste a token above and three panels open. Header and payload come back pretty-printed at two spaces. The signature is left as it arrived, a raw base64url string, because it is a hash with no text inside to show. Underneath, the registered time claims are read out properly. exp, iat, and nbf count seconds since 1970, which nobody reads at a glance, so each gets a UTC timestamp and a verdict in words: expired 3h 12m ago, valid for another 6d 4h, not valid yet. Encode mode runs the other way, signing a payload with HS256, HS384, or HS512.

When to use it

The most common reason to land here is a 401 that makes no sense. The request worked an hour ago, nothing in the code changed, and the API is refusing the same token. Copy the value out of the Authorization header, paste it in, read exp. Nine times out of ten it has passed, and the real answer is that your access token has a fifteen minute lifetime and the refresh path stopped running. A leading Bearer prefix is stripped and line breaks ignored, so paste the header value whole.

The second case is finding out what an identity provider issues in practice, rather than what its documentation describes. Auth0, Cognito, Okta, Entra ID, and Keycloak all shape tokens differently, and the claim your middleware looks for may be spelled another way, nested under a namespace, or missing because a scope was never requested. Reading one real token settles it. Role and group claims are the usual culprit: often opt-in, and an empty array behaves differently from an absent key.

Encode mode covers the other half. Integration tests need tokens carrying specific claims, and hand-building one is tedious. Set the payload, pick HS256, sign with the secret your test server verifies against. It is also the fastest way to show a colleague why a payload is a poor place to keep anything private: sign a token, then decode it without the secret and watch it read straight back. Decoded payloads are JSON like any other, so the JSON Formatter will re-indent one for a bug report, and the JSON Parser will name the type of a claim value you are unsure about.

How this tool works

Decoding is a split, a base64url decode, and a JSON parse, done twice. base64url is ordinary base64 with two substitutions, minus for plus and underscore for slash, and the trailing equals-sign padding removed. That is what lets a token sit in a URL unescaped. The decoded bytes are read as UTF-8 rather than one byte per character, so a claim carrying accented or non-Latin text survives instead of arriving as mojibake. Segments are reported separately: when the payload is not JSON you still get the header, plus the decoded text. Three correct segments with a middle chunk that is not JSON usually means a token truncated in a log field, or one that picked up a second layer of encoding in a queue.

Encode mode builds the header for you, since an HMAC header only ever carries two fields, then base64url-encodes header and payload, joins them with a dot, and signs that string with the browser's own Web Crypto HMAC implementation. No library is involved, and your secret is used as raw key bytes, as the specification calls for. Only HS256, HS384, and HS512 are offered, for the reason in the FAQ below.

Nothing leaves the tab. Decoding, claim inspection, and signing all run locally in this browser tab, and no request carries the token anywhere. That matters more here than on almost any other utility, because a JWT is not a document. It is a live credential. Anyone holding one can act as its subject until it expires, so pasting a token into a hosted decoder hands a working key to a server you do not control.

Examples

  • A 401 that turns out to be an expired token

    Input
    Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhdXRoMHw2NGYxYzIiLCJlbWFpbCI6ImFkYUBleGFtcGxlLmNvbSIsImV4cCI6MTc2NzIyMjAwMCwiaWF0IjoxNzY3MjE4NDAwfQ.hzkecv-FbfiG8NTLntgOpIsJcSPXcxTFd94PWqpsiTQ
    Output
    {
      "sub": "auth0|64f1c2",
      "email": "ada@example.com",
      "exp": 1767222000,
      "iat": 1767218400
    }

    The Bearer prefix is stripped for you. The claims panel turns those epochs into 2025-12-31 22:00:00 UTC and 2025-12-31 23:00:00 UTC, then marks the token expired, counting from the moment you decode.

  • The header decodes, the payload does not

    Input
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.dGhpcyBpcyBwbGFpbiB0ZXh0LCBub3QganNvbg.hzkecv-FbfiG8NTLntgOpIsJcSPXcxTFd94PWqpsiTQ
    Output
    The payload (segment 2) decoded cleanly but its contents are not valid JSON. Decoded text: this is plain text, not json

    Three segments, valid base64url throughout, and still not a usable JWT. The header above the error decodes fine and reports alg HS256, which is the tell: the shape is right and only the middle chunk is wrong.

  • A token that lost a segment on the way

    Input
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxfQ
    Output
    A JWT has three dot-separated segments (header.payload.signature). This input has 2.

    Copying out of a wrapped terminal or a truncated log field is the usual cause. Count the dots before going looking for anything cleverer.

  • Signing a payload with HS256

    Input
    Payload: {"sub":"1234567890","role":"admin","iat":1735689600}
    Secret: zos-demo-secret
    Output
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzM1Njg5NjAwfQ.L5dEdAsxjpPdG_iqjvlwSUGqpj8Mu-7kIk8hlYvZ8lc

    The header is generated as alg HS256 and typ JWT, then encoded for you. Look at the signature: no equals signs on the end, and an underscore where standard base64 would put a slash. That is base64url. Paste this token into decode mode and the payload reads straight back, no secret needed.

  • An unsigned token, which is a different problem

    Input
    eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhbm9uIiwicm9sZSI6ImFkbWluIn0.
    Output
    Empty, and the header declares alg "none". This token is unsigned and anyone can rewrite its payload.

    The payload still decodes and still claims an admin role. Nothing vouches for it. If your API ever accepts a token like this, the bug is in the verifier: alg must be pinned by the server, never read from the token being checked.

Frequently asked questions

  • What are the three parts of a JWT?

    Header, payload, and signature, joined by dots and each encoded as base64url. The header names the algorithm and token type. The payload holds the claims: sub for the subject, iss for the issuer, aud for the audience, exp and iat and nbf for timing, plus custom claims your provider adds. The signature is a hash over the first two parts, computed with a secret or a private key. It is what makes the other two trustworthy: change one character of the payload and the signature stops matching.

  • Why can anyone read my JWT payload?

    Because base64 is an encoding, not encryption. It exists so arbitrary bytes can cross systems that only handle text, and reversing it needs no key. A signed JWT is protected against tampering, not against reading. Put in it only what you would be happy to see in a devtools panel or an access log. An internal user ID and a list of roles, fine. A home address, a national ID number, or an API key for another service, no. Payloads that must be unreadable need JWE, a different construction from the JWS tokens this tool handles.

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

    Treat the question the way you would treat pasting a password. An unexpired access token is a working credential, and whoever receives it can use it until it expires. This decoder runs the split, the base64url decode, and the JSON parse in your browser, so the token never crosses the network. Prefer a client-side tool over one that posts your token somewhere for processing, and when unsure about any decoder, feed it an expired token instead.

  • Why does this tool not verify the signature?

    Verification needs the signing secret or the issuer's public key, and a web page is the wrong place to type either. A secret pasted into a browser tab is one you now have to rotate. There is a subtler reason too. A checkmark from a web page is not a security control. Verification belongs in the code path that grants access, where the algorithm is pinned, issuer and audience are checked, and the key comes from a JWKS endpoint you trust. A decoded payload tells you what a token claims, never whether the claim is real.

  • How do I tell whether a JWT has expired?

    Read the exp claim, a NumericDate: seconds since 1 January 1970 UTC. Paste your token above and the claims panel shows the raw number, the same instant as a UTC timestamp, and whether it has passed, with the gap in days, hours, and minutes. A common trip-up is a value in milliseconds, left over from a JavaScript timestamp. It shows up as a date thousands of years away, and the tool says so. Check nbf too: a token can be well-formed, unexpired, and still rejected for not being valid yet.

  • Why are RS256 and ES256 missing from the algorithm list?

    They sign with a private key, which would have to be pasted in as PEM or JWK to be usable. Nothing good comes from encouraging that habit, even in a tool that runs locally, because the habit outlives the tool. HS256, HS384, and HS512 are here because a shared secret is what test fixtures and internal services normally use. Mint an RS256 token where the key already lives: your identity provider, or a KMS. Decoding an RS256 token here works as well as decoding any other, since decoding never touches a key.

  • Can you build us a claims inspector for our own issuers?

    Yes, and it is a recurring request. Pasting tokens into a decoder is fine while you chase a single 401. It stops being fine when every support ticket starts with someone manually checking a customer's claims, or when nobody can say which of five services issued the token that failed. Zinc Online Solutions builds the tooling that closes that gap: a claims inspector wired to your own issuers, a token-shape check in CI, or a support console that answers the expiry question without anyone touching a live credential. Describe how a failed token gets diagnosed on your team right now.