privileged.id

Tool · JWT

JWT decoder and inspector

Paste a JWT to see its header and claims explained, with warnings for expiry, risky algorithms and missing audience. Optionally verify the signature locally with a public key.

Runs in your browser. Nothing you enter is sent or stored.Reviewed

Examples:
Verify the signature here (optional)

Uses your browser's WebCrypto. The key never leaves this page. Paste a public key: a JWK, a JWK Set (matched by kid), or a PEM PUBLIC KEY or CERTIFICATE. HMAC tokens need the shared secret: only do that for test tokens.

Nothing leaves this page

Decoding a JWT is just base64url and JSON: no server is needed, so none is used. The page's Content Security Policy only lets its own scripts run and only lets them fetch files from this site, so a pasted token has nowhere to go. Signature checks use the browser's built-in WebCrypto API. Nothing is stored: reload the page and it is empty again.

Anatomy of a JWT

A signed JWT (a JWS in compact serialization) is three base64url strings separated by dots: a header naming the algorithm and key, a payload of claims, and a signature over the first two. Anyone can decode the first two parts; only the signature check tells you who wrote them. Here is RFC 7519's own example, decoded:

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Header

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

Payload

{
  "iss": "joe",
  "exp": 1300819380,
  "http://example.com/is_root": true
}

exp is 1300819380 seconds after 1970-01-01 UTC: 22 March 2011, 18:43 UTC. The token expired long ago, which the decoder flags. The signature is an HMAC-SHA256 over header.payload with the key given in RFC 7515 Appendix A.1; choose the first example above to see it verify.

What the decoder checks

Each finding links to the sentence in the standard behind it.

CheckWhySource
alg is none (any letter case)An unsigned token can be written by anyone. Libraries should not accept it unless the caller explicitly asks.RFC 8725 §3.2
Duplicate header or claim namesNames MUST be unique. Two parsers that resolve a duplicate differently can disagree about who the token is for.RFC 7519 §4
exp, nbf, iatExpired or not-yet-valid tokens MUST NOT be accepted. Dates must be numbers of seconds; values in milliseconds are flagged.RFC 7519 §4.1.4–4.1.6
Missing audWhen an issuer serves more than one recipient, the token MUST carry an audience and each recipient MUST check it.RFC 8725 §3.9
jwk, jku, x5u, odd kidKeys or key URLs supplied by the token itself must not be trusted blindly: SSRF, injection and self-signed tokens.RFC 8725 §3.10
typExplicit typing is recommended; at+jwt marks an OAuth access token whose required claims are then checked.RFC 8725 §3.11, RFC 9068 §2.2
Signature lengthECDSA signatures in JWS are raw R‖S (64 bytes for ES256), not DER. A DER signature is a common interoperability bug.RFC 7518 §3.4
ID-token claimsnonce, at_hash or azp suggest an OpenID Connect ID token, which is for the client, not for APIs.OIDC Core §2
Personal data in claimsSigning is not encryption: anyone holding the token can read it.RFC 7519 §12

How verification works here

Verification takes the algorithm from the token, but refuses to proceed when the key does not fit it: an RSA or EC key offered for an HS256 token is rejected, as is a JWK whose own alg says something else. That mismatch is the root of the classic algorithm-confusion attack, where a server's RSA public key is used as an HMAC secret (RFC 8725 §2.1). Supported: HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512 and EdDSA (Ed25519, where the browser supports it). If you paste a JWK that contains private members such as d, only the public part is used, and the page warns you.

A valid signature only proves the token was signed by the holder of that key. Your service must still decide whether it trusts that key for that issuer, whether the algorithm is on its allow-list, and whether the audience, expiry and type are right for the request in hand. The checks run on every build against RFC 7515 and RFC 8037's example tokens; results are on Sources and method.

What a server must do that a decoder can't

  1. Pin the algorithms it accepts, per key, in configuration; never let the token's alg choose (RFC 8725 §3.1).
  2. Get keys from the issuer's configuration (for OpenID Connect, its discovery document's jwks_uri), and check the key belongs to the iss in the token (§3.8).
  3. Check aud names this service, and exp/nbf against its own clock with a small allowance for skew.
  4. Check the token's type and required claims for its purpose, so an ID token or a token for another API is not accepted (§3.12).
  5. Then authorize: the token says who and what scopes; the service still decides whether this request is allowed.

Sources

  1. RFC 7519: JSON Web Token (JWT), IETF. IETF Trust Legal Provisions (brief quotations with attribution).
  2. RFC 7515: JSON Web Signature (JWS), IETF. IETF Trust Legal Provisions (brief quotations with attribution).
  3. RFC 7518: JSON Web Algorithms (JWA), IETF. IETF Trust Legal Provisions (brief quotations with attribution).
  4. RFC 8725: JSON Web Token Best Current Practices, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
  5. RFC 9068: JWT Profile for OAuth 2.0 Access Tokens, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
  6. RFC 8037: CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JOSE, IETF. IETF Trust Legal Provisions (brief quotations with attribution).
  7. OpenID Connect Core 1.0, OpenID Foundation. OpenID Foundation specification (cited, brief quotations).