induwara.lk
induwara.lkDeveloper · Tokens

JWT Decoder — inspect & verify JSON Web Tokens

Paste any JSON Web Token to decode the header, payload, and signature. Each claim is classified (registered, public, private), expiry is surfaced in plain English, and HS256 signatures verify in your browser via the Web Crypto API. RFC 7519 / 7515 conformant, no uploads, no signup.

By Induwara AshinsanaUpdated May 11, 2026
JWT DecoderRFC 7519

Never paste production tokens. Decoding happens in your browser — nothing is uploaded — but a real token reveals who someone is, what they can access, and when their session expires. Use a test token, or rotate the live one straight after.

A 'Bearer ' prefix is stripped automatically. Paste anywhere.
Samples

Sources: structure follows RFC 7519, signature handling RFC 7515, algorithm names RFC 7518. HS256 verification uses W3C SubtleCrypto. Everything runs in your browser — no uploads, no logs.

How it works

A JSON Web Token (RFC 7519) is a compact, URL-safe representation of a set of claims. The serialised form is three base64url-encoded segments joined by a literal . character: the header declares the signature algorithm, the payload carries the claims, and the signature covers the bytes of base64url(header).base64url(payload). The decoder on this page implements the three reading steps and the one cryptographic step exactly as the spec lays them out:

  1. Split on ‘.’. A JWS (signed JWT) has exactly 3 segments; a 5-segment token is JWE (RFC 7516) and not in scope here. The decoder also strips a leading Bearer so you can paste straight from an Authorization header.
  2. Base64url-decode each segment. Base64url is RFC 4648 §5 — the standard Base64 alphabet with - in place of + and _ in place of /. RFC 7515 Appendix C drops the = padding. This decoder accepts both forms (with and without padding) and runs every segment through a second atob-based implementation to confirm the two routines agree byte for byte.
  3. UTF-8 decode + JSON parse. The header and payload bytes are interpreted as UTF-8 by a fatal-mode TextDecoder (malformed bytes raise a clear error rather than silently turning into U+FFFD), then handed to JSON.parse. Each top-level key is then classified against the registered claim names in RFC 7519 §4.1 (payload) and RFC 7515 §4.1 (header) — so the UI can distinguish standard iat from your application's custom tenant_id.
  4. Verify the signature (HS256). If the header declares alg: HS256 and you provide the shared secret, the decoder hashes the ASCII bytes of segment[0].segment[1] with HMAC-SHA-256 via crypto.subtle.verify. You can supply the secret as UTF-8, hex, base64, or base64url — high-entropy keys are rarely printable ASCII. RS256 / ES256 / PS256 need the issuer's public key, not a shared secret, so the decoder shows the claims but refuses to mark the signature as verified.

Two independent invariants run on every page load to keep the implementation honest. First, the RFC 7515 §A.1 canonical HS256 test vector must decode to iss=joe, exp=1300819380 and alg=HS256. Second, every base64url segment is decoded twice — once by the bit-shift routine and once by an atob-based alternative — and the byte arrays must match. Both checks are surfaced as the green “Verified · RFC 7515 §A.1” badge in the decoder header. A red badge means a real regression — please email me.

Worked examples

RFC 7515 §A.1 — the canonical HS256 vector

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9 . eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ . dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

  1. Split on "." → 3 segments (header, payload, signature)
  2. base64url-decode segment 0 → {"typ":"JWT", "alg":"HS256"}
  3. base64url-decode segment 1 → {"iss":"joe", "exp":1300819380, "http://example.com/is_root":true}
  4. exp = 1300819380 → 2011-03-22T18:43:00Z → expired
  5. Signature is 32 bytes (matches HMAC-SHA-256 output size)
  6. Verify with the spec-given secret (base64url-decoded) → ok: true

Modern auth token with iss, sub, name, iat, exp

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkluZHV3YXJhIEFzaGluc2FuYSIsImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoxOTAwMDAwMDAwLCJpc3MiOiJpbmR1d2FyYS5sayJ9 . -2XFoL4gTP0EpFES9rexX1egi73hDUO4kBPrz-tuQJ0

  1. Header = {"alg":"HS256","typ":"JWT"}
  2. Payload = {"sub":"1234567890", "name":"Induwara Ashinsana", "iat":1700000000, "exp":1900000000, "iss":"induwara.lk"}
  3. iat = 2023-11-14 22:13:20 UTC; exp = 2030-03-17 14:26:40 UTC
  4. Sub / name / iss are surfaced as registered or known claims
  5. Verify with the secret "induwara-secret" (UTF-8) → ok: true

Asymmetric token (RS256) — decode works, verify cannot

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiJhbGljZSIsImlhdCI6MTcwMDAwMDAwMCwic2NvcGUiOiJyZWFkOmRvY3Mgd3JpdGU6ZG9jcyJ9 . <RSA-2048 signature, 256 bytes>

  1. Decoder splits + base64url-decodes both segments identically
  2. Header = {"alg":"RS256","typ":"JWT"} → flagged asymmetric
  3. Payload = {"sub":"alice","iat":1700000000,"scope":"read:docs write:docs"}
  4. Signature segment is parsed but cannot be verified with a shared secret
  5. UI shows the claims and links to the JWKS workflow

Frequently asked questions

Sources & references

Related tools

Rate this tool
Be the first to rate

Comments & feedback

Spotted a bug or want an improvement? Tell us — our team reviews every comment, and good ideas get built. Comments are public and anonymous.

Found a bug, edge case, or want to suggest an improvement?

Email me at [email protected] — most fixes ship within 24 hours.