Tools
Guides

JWT Decoder

Dev

Decode a JSON Web Token (header, payload) and inspect its expiry and signature. Decoding only — the signature is not verified.

100% client-side No backend
Header
 
Payload
 
Issued at (iat)
Not before (nbf)
Expires (exp)
Signature
On this page

What is a JWT?#

A JWT (JSON Web Token, defined in RFC 7519) is a compact, URL-safe token built to carry claims between two parties. It is the credential your browser sends to an API after login, the identity assertion an OAuth provider hands a relying application, and the envelope most modern session and access-control flows travel in. The appeal is that the claims — who the user is, what they can do, when the token expires — travel inside the token itself as JSON, so the receiving service can read them without a database lookup.

A JWT has exactly three base64url-encoded parts separated by dots: header.payload.signature. The header names the algorithm and token type. The payload is a JSON object of claims — registered ones like sub (subject), iat (issued at), exp (expiration), nbf (not before), plus any custom claims your application adds. The signature is what the issuer computed over the other two parts with a secret or private key; verifying it is what proves the token was not tampered with.

This page decodes and inspects a JWT. It splits the three parts, base64url-decodes the header and payload into readable JSON, surfaces the standard time claims as human-readable timestamps, and shows a live status badge (Valid, Expired, Not yet valid). It deliberately does not verify the signature — that requires the issuer’s secret or public key and is out of scope for a 100% client-side tool. The signature segment is displayed raw, exactly as received.

How to use it#

  1. Paste your token into the input box on the left. It must be three dot-separated segments; whitespace around it is trimmed automatically.
  2. Click Sample to load a built-in example token (with iat/exp/nbf) if you just want to see how the layout works.
  3. Click Clear to empty the input and the result panes.
  4. Read the right-hand panel:
    • The status badge at the top — green Valid, Expired, or Not yet valid — based on the nbf and exp claims compared to the current time.
    • The Header card (with the algorithm shown in its title bar, e.g. HS256).
    • The Payload card, pretty-printed as JSON.
    • The time-claims list — Issued at, Not before, Expires — each rendered as both a unix timestamp and a readable date.
    • The Signature segment, shown raw because it is still base64url-encoded bytes.
  5. If the token is malformed, the red status line tells you whether it failed the three-segment shape check, the base64url decode, or the JSON parse.

Key features#

  • Live time-claim status. Reads iat, nbf, and exp and tells you whether the token is currently valid, already expired, or not yet in effect — the three questions you actually ask during a login bug.
  • Human-readable dates. Unix epoch numbers like 1700000000 are shown next to the UTC date they represent, so you stop doing mental epoch arithmetic.
  • Algorithm surfaced. The header’s alg value is pulled into the Header card’s title bar, so you immediately see whether you are holding an HS256, RS256, or something unexpected.
  • UTF-8 safe decoding. Base64url segments are decoded as bytes through TextDecoder, so non-ASCII claims (names, roles in other scripts) render correctly instead of as mojibake.
  • Decode only — never verify. The tool never asks for a secret, which makes it safe to paste tokens you do not fully trust: nothing is sent anywhere, and the signature is shown but not checked.

Worked example#

Click Sample and the input fills with a token whose header decodes to:

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

and whose payload decodes to:

{
  "sub": "1234567890",
  "name": "ArpGate Demo",
  "iat": 1700000000,
  "exp": 4102444800,
  "nbf": 1699999000
}

The time-claims list then shows:

Issued at   1700000000  (2023-11-14 22:13:20 UTC)
Not before  1699999000  (2023-11-14 21:56:40 UTC)
Expires     4102444800  (2100-01-01 00:00:00 UTC)

The status badge reads Valid, because the current time is after nbf and before exp. The signature segment is shown as the literal string c2FtcGxlLXNpZ25hdHVyZS1ub3QtdmVyaWZpZWQ — which decodes to the text “sample-signature-not-verified”, a deliberate marker that this token’s signature is illustrative, not a real HMAC-SHA256 output.

That last point is the single most important thing to understand about this tool: a token that decodes cleanly is not necessarily a token that is trustworthy. Anyone can forge the header and payload; only the signature, checked against the issuer’s secret or public key, proves authenticity. Use this page to read claims and debug time windows; verify signatures in your actual backend.

FAQ#

Can this tool tell me if a JWT is authentic?#

No, and that is by design. Decoding only reads the JSON inside the token — it does not prove the token was issued by who claims to have issued it. To verify authenticity you need the alg-appropriate key (a shared secret for HS256, the issuer’s public key for RS256/ES256) and a verification routine on a trusted backend. Never treat a cleanly decoded token as a verified one.

The status says “Expired”. Can I refresh the token here?#

No. Refreshing means calling your authentication server’s refresh endpoint to get a new access token — a network operation this static, client-side page cannot and should not perform. What you can do here is confirm exactly when the old token expired, which is usually the clue you needed.

What is the difference between iat, nbf, and exp?#

iat (issued at) is when the token was created — informational. nbf (not before) is the earliest time the token should be accepted; before nbf a validating server should reject it even if the signature is valid. exp (expiration) is the hard cutoff after which the token must be rejected. A well-formed token has nbf ≤ iat ≈ now and exp somewhere in the near future (minutes for access tokens, longer for refresh tokens).

My payload contains non-ASCII characters and they look garbled elsewhere. Why are they right here?#

Because some decoders mistakenly treat each base64url character as a byte instead of decoding to raw bytes first. This page decodes to a Uint8Array and then runs it through TextDecoder, which interprets the bytes as proper UTF-8. That is the correct way, and it is why names, roles, and scopes in any script render as written.