What a JWT is
A JSON Web Token (RFC 7519) is three Base64url-encoded segments separated by dots: a header that names the signing algorithm, a payload of claims, and a signature over the first two. The header and payload are not encrypted, only encoded. Anyone holding a token can read its contents, which is exactly what this tool does.
{"alg": "HS256", "typ": "JWT"}{"sub": "1234567890", "name": "John Doe", "iat": 1516239022}Decoding is not verification
Decoding shows what the token says. It does not tell you whether the token was issued by the party it claims, or whether it has been modified. That requires checking the signature with the issuer's key: a shared secret for HMAC algorithms (HS256), or the issuer's public key for RSA and ECDSA algorithms (RS256, ES256). This tool only reports “Signature valid” after actually running that check with a key you provide. Without one it says, plainly, that the signature has not been verified.
Two rules for server-side code follow from this. Never trust claims from a token you have not verified. And never let the token choose the algorithm: reject alg: none and pin the algorithms you expect, otherwise an attacker can downgrade an RSA-signed token to an HMAC one “signed” with your public key.
Registered claims
| Claim | Meaning | Notes |
|---|---|---|
iss | Issuer | Who created the token. Verify it matches the expected authority. |
sub | Subject | The principal the token is about, usually a user ID. |
aud | Audience | Who the token is for. A string or an array. Reject tokens not addressed to your service. |
exp | Expiration time | Unix seconds. The token is invalid at or after this time. |
nbf | Not before | Unix seconds. The token is invalid before this time. |
iat | Issued at | Unix seconds. Useful for detecting tokens that are too old even if not yet expired. |
jti | JWT ID | Unique identifier, used to reject replays. |
Timestamps are in seconds, not milliseconds. A 13-digit value is a bug in the issuer. The claims table above converts each timestamp to UTC and to your local time and says how far in the past or future it is. The Unix Timestamp Converter does the same for arbitrary values.
Common problems
- “Expected three segments.” The token was truncated, has whitespace in it, or is a JWE (five segments, encrypted). A
Bearerprefix is stripped automatically. - Header or payload will not decode. The segment is not Base64url, or it decodes to something that is not JSON. Opaque tokens from some providers look like JWTs but are not.
- Expired. Check the server clock too: a few seconds of skew is normal, and most libraries accept a small leeway.
- Signature does not match with the right secret. Some frameworks store the HMAC secret Base64-encoded and decode it before signing. Tick the Base64 option and try again.
Verifying in code
Node.js (jose)
import { jwtVerify, createRemoteJWKSet } from "jose";
const JWKS = createRemoteJWKSet(new URL("https://issuer.example.com/.well-known/jwks.json"));
const { payload } = await jwtVerify(token, JWKS, {
issuer: "https://issuer.example.com",
audience: "my-api",
algorithms: ["RS256"],
});
Python (PyJWT)
import jwt
payload = jwt.decode(token, key, algorithms=["HS256"], audience="my-api")
# Decode without verifying, for debugging only:
jwt.decode(token, options={"verify_signature": False})
Privacy
Decoding uses a local Base64url decoder and JSON.parse; verification uses crypto.subtle. No part of the token, secret or key leaves your browser. Tokens are credentials: treat any browser tab where you paste one as you would a terminal, and prefer expired or test tokens when you can.