JWT Decoder
Decode JWT tokens and inspect their claims. Runs locally — nothing is uploaded.

Utilities

JWT Decoder

Decode JWT tokens and inspect header, payload, and signature claims.

The JWT Decoder takes a header.payload.signature token and unpacks it into a Claims Summary, pretty Header and Payload JSON blocks, and the raw Signature segment — all without verifying anything. Paste a token (or click "Load example" for a canned HS256 JWT with sub/name/iat/exp/iss/role claims) and after a 150 ms debounce you get an expiry pill ("Valid for {duration}", "Expired {duration} ago", or "No expiry (exp) claim") plus claim rows for Algorithm, Type, iss, sub, aud, jti, iat, nbf, and exp — missing values show "—".

Each of Header, Payload, and Signature has its own Copy button; iat/nbf/exp timestamps render as both raw seconds and locale-formatted dates, and audience arrays are joined with ", ". Errors are precise: "Empty input", "Invalid JWT: expected 3 dot-separated segments, found {n}", "Invalid {header|payload}: not valid Base64URL", or "Invalid {header|payload}: not valid JSON". The card is explicit that it "Runs locally — nothing is uploaded" — and just as explicitly, it never verifies signatures.

How it works

lib/jwtDecoder.ts splits the token on "." (requiring exactly 3 segments), validates each part against the Base64URL alphabet /^[A-Za-z0-9_-]+$/, then decodes by mapping -→+, _→/, adding padding, calling atob, and running the bytes through TextDecoder before JSON.parse — which must yield a non-array object. getClaimSummary extracts iss/sub/aud/jti/iat/nbf/exp; getJwtAlgorithm/getJwtTokenType read header alg/typ with "Unknown"/"JWT" defaults.

getExpiryInfo compares exp*1000 against Date.now() and formats the delta via s → m → h → "Xd Yh" for the pill label. Decoding is debounced 150 ms after each keystroke to avoid thrashing on long pastes. Signature bytes are shown raw for inspection only — there is no key handling, no alg check, and no HMAC/RSASSA computation anywhere in the pipeline.

How to use

  1. Paste a JWT into the textarea (placeholder "Paste a JWT token (header.payload.signature)…") or click "Load example".
  2. Wait for the 150 ms debounce — Claims Summary renders if valid; otherwise an Alert shows the precise error.
  3. Check the expiry pill: "Valid for …", "Expired … ago", or "No expiry (exp) claim".
  4. Review the claim rows: Algorithm, Type, Issuer (iss), Subject (sub), Audience (aud), JWT ID (jti), Issued At (iat), Not Before (nbf), Expires At (exp) — missing values show "—".
  5. Expand the "Header" and "Payload" pretty JSON blocks (max-height scrollable) and use each section's Copy button as needed.
  6. Read the raw "Signature" segment — display only, no verification is performed.
  7. Click "Clear" to empty the token and reset the view.

Tips

  • This decoder only decodes — it never verifies signatures or checks alg against a key, so treat decoded claims as untrusted input.
  • iat, nbf, and exp show both the raw seconds value and a toLocaleString() human date for cross-checking.
  • Audience arrays are joined with ", " in the claim row, so multi-audience tokens read cleanly.
  • Empty input returns a friendly "Empty input" rather than the segment-count error, so a cleared box doesn't look broken.

JWT Decoder FAQ

Does it verify the signature?

No — only Base64URL decode + JSON parse of header and payload; the signature is displayed raw for inspection and never checked.

What if the token doesn't have 3 segments?

You get "Invalid JWT: expected 3 dot-separated segments, found {n}" — the decoder requires the standard header.payload.signature shape.

How is expiry calculated?

getExpiryInfo compares exp * 1000 against Date.now() and labels the pill "Valid for …" or "Expired … ago" using an Xd Yh formatter.

Is the token uploaded?

No — the card says "Decode JWT tokens and inspect their claims. Runs locally — nothing is uploaded." Decoding is pure atob/TextDecoder/JSON.parse in the browser.

Credits

ShareTweet
More Utilities

Command Palette

Search for a command to run...