JWT Decoder & Verifier
Decode any JWT into header, payload and signature. Verify HMAC signatures inline with a shared secret. Inspect every standard claim with its RFC reference. Runs locally in your browser.
Learn More
A JWT (JSON Web Token RFC 7519) is three base64url-encoded JSON objects glued together with dots: header.payload.signature. Any of those segments decode trivially with the same atob you'd use on a normal Base64 string — the encoding is purely transport not encryption. The header is a small JSON object that almost always contains exactly two fields: alg (the algorithm used to compute the signature e.g. HS256 RS256 ES256) and typ (literally the string JWT ). The payload is the claims object — a map of arbitrary key/value pairs with seven well-known registered claims defined by the spec (iss sub aud exp nbf iat jti) and any number of custom claims layered on top. The signature is computed across the first two segments using the algorithm in the header with either a shared secret (HMAC family) or a private key (RSA ECDSA EdDSA families). The key point most JWT bugs hinge on: decoding does NOT validate. Any tool including this one can decode any JWT without a key. Decoding turns the bytes into JSON. Validation requires the key the issuer used to sign — and validation is what tells you whether the token is genuine. This decoder shows the contents (which is what you usually need for debugging) and inline-verifies HMAC signatures when you supply the secret. For RSA/ECDSA the verification path is the same logic but requires the public key which is too cumbersome for a paste-into-a-browser tool — fetch the key from your IdP's JWKS endpoint and use a library.
RFC 7519 defines seven claims that have agreed-upon meanings across implementations. Every JWT-aware system understands them; deviating produces interop bugs. iss (Issuer) identifies the principal that issued the token — usually a URL like https://accounts.google.com or an opaque issuer ID. Receivers should match it against a fixed allow-list never accept whatever the token claims. sub (Subject) identifies the principal the token is about — a user ID scoped within the issuer. aud (Audience) identifies the recipients the token is intended for: receivers MUST reject the token if their identifier isn't present otherwise tokens issued for one service can be replayed against another. The three time claims govern validity. exp (Expires) and nbf (Not before) are mandatory checks: receivers MUST reject tokens past exp or before nbf with a small clock skew tolerance (30-60 seconds is typical). iat (Issued at) is informational useful for logging and for revocation strategies that need to know when a token was minted. jti (JWT ID) is a unique identifier per token used to enforce single-use semantics — store the jti in a short-lived cache or revocation list and reject any token whose jti has been seen before. Most JWT bugs in production come from one of: forgetting to validate aud (token confusion attacks) forgetting to validate exp (expired tokens accepted indefinitely) or trusting decode() instead of verify() (any signature accepted). This decoder surfaces every claim with its meaning so you can audit your token flows by inspection.
JWTs solved a specific problem so well they became the default: how do you authenticate stateless requests across multiple services without round-tripping to a session database? Sign a small token with the user's identity hand it to the client and any service that knows the signing key can verify it locally. No shared session store no auth-server bottleneck no chatty service-to-service calls just to check who the user is. This is why JWTs are the universal token format for OAuth 2.0 access tokens OpenID Connect ID tokens AWS Cognito session tokens Auth0 access tokens Firebase ID tokens Keycloak sessions and most service-to-service mesh authentication. The model breaks down at the edges. Revocation is the big one: stateless tokens are valid until they expire period — there's no reliable way to invalidate a token mid-session. Industry workarounds include short expiry windows (5-15 minutes) with refresh tokens JTI-based revocation lists (defeats the stateless win for the verifier) or fully bespoke session systems that issue JWTs but check a database anyway (defeats the entire premise). The other class of failure is over-stuffing the payload: it ends up in URLs request headers and cookies and at multi-kilobyte sizes you start hitting header limits in load balancers and reverse proxies. Keep the payload small — ID audience scope expiry — and look up everything else server-side from those.
Frequently asked questions
Decoding is computed entirely in your browser — the token never goes to our servers never appears in logs never crosses the network. That said JWTs are bearer tokens: anyone who has the string can act as the token's subject until it expires. Even pasting it into a browser tab (any tool ours or jwt.io) means trusting the page not to exfiltrate. The safer pattern for production tokens is to decode them locally with a CLI: echo $TOKEN | cut -d. -f2 | base64 -d | jq works on any Unix machine and never touches the browser.
Decoding and verification are independent — JWT segments are base64url-encoded not encrypted so anyone can decode any token without a key. Verification checks that the signature was produced with the secret you provided. If you see 'Signature does not match' either the secret is wrong the algorithm in the header doesn't match how the token was actually signed (some implementations sign with HS256 but advertise HS512) or the JWT has been tampered with after issuance. Compare the algorithm displayed in the Header pane against what your issuer documentation says.
Not directly — RSA and ECDSA verification requires a public key in JWK or PEM format which is too unwieldy to type into a browser tool. For asymmetric tokens use the decoded header's kid claim to fetch the matching key from your identity provider's JWKS endpoint (typically /.well-known/jwks.json) then verify with a library: jose for Node python-jose for Python golang.org/x/oauth2/jws for Go. The decoder here still shows you the header payload and registered-claim explanations regardless of algorithm.
JWT (RFC 7519) is the data format — three base64url-encoded JSON segments separated by dots. JWS (RFC 7515) is the JSON Web Signature spec defining how the signature segment is computed and verified. JWE (RFC 7516) is the JSON Web Encryption spec for encrypting the payload (rare in practice — most JWTs are signed-but-not-encrypted with sensitive data kept server-side). JWA (RFC 7518) is the algorithm catalogue: HS256 RS256 ES256 etc. Most code paths people call JWT are actually JWS — the payload is signed but readable to anyone.
Two common causes. First the receiver isn't validating exp — JWT libraries sometimes default to decode-without-verify especially when developers cargo-cult example code. Verify the receiver actually calls jwt.verify() (not jwt.decode()) and passes the secret. Second clock skew tolerance: most validators accept tokens up to 30 or 60 seconds past expiration to handle distributed clock drift. If you're seeing minutes of slack the receiver's clock skew config is too generous or one of the machines has a wildly wrong clock.
alg=none is a JWT signature scheme that means this token has no signature — the spec allows it but every receiver MUST reject it unless explicitly configured otherwise. Vulnerable libraries accept a token with alg=none and an empty signature segment as if it had a valid signature letting an attacker forge any payload. The mitigation is to always pass the EXPECTED algorithm to your verifier (e.g. jwt.verify(token key algorithms: ['HS256'] ) so unsigned tokens are rejected. This decoder displays alg=none clearly with a verification disclaimer but it doesn't make decisions about validity — that's your application's responsibility.
Decode the token find the alg field in the header and verify the signature with the matching key. If verification fails the token has either been tampered with signed by a different key or signed with a different algorithm than the header advertises. Tampering only makes sense in attacker scenarios because the receiver — if doing its job — will reject any tampered token immediately. The signature segment doesn't have to look meaningful to the eye: it's a binary signature base64-encoded so its content is opaque. The only validity check is the verifier.
More in Data Utilities
Developer validators, formatters and generators for structured data and identifiers.