JWT Decoder — Read Header & Payload
Decode a JSON Web Token and read every claim, with expiry timestamps rendered as dates. The token is never transmitted — it is decoded in your browser.
Output will appear here
The registered claims
RFC 7519 reserves these seven names. All are optional, but they mean specific things and libraries act on them.
| Claim | Name | Meaning |
|---|---|---|
iss | Issuer | Who created the token. Verify it — a valid signature from the wrong issuer is still wrong |
sub | Subject | Who the token is about, typically a user ID |
aud | Audience | Who the token is for. A token minted for service A must be rejected by service B |
exp | Expiration | Unix seconds after which the token must be rejected |
nbf | Not before | Unix seconds before which the token must be rejected |
iat | Issued at | Unix seconds when the token was created |
jti | JWT ID | Unique identifier, used to detect replay or support revocation lists |
exp, nbf and iat are in seconds, not milliseconds. Passing a JavaScript Date.now() value directly produces a token that expires in the year 56,000 — a real and frequent bug.
JWT implementation mistakes that become vulnerabilities
alg: none accepted
Cause:The specification permits an "unsecured" JWT with alg set to none and an empty signature. A verifier that reads the algorithm from the token itself will accept a forged token with any payload.
Fix:Never let the token choose its own verification algorithm. Configure the expected algorithm server-side and reject anything else. Most modern libraries do this by default; older ones did not, and this produced a wave of CVEs.
RS256 token verified with HS256
Cause:An attacker takes your public RSA key — which is public — and uses it as an HMAC secret to sign a forged token with alg changed to HS256. A verifier that trusts the header algorithm validates it successfully.
Fix:Same root cause as above: pin the algorithm server-side. This is why "just read alg from the header" is never acceptable.
Signature valid but token was for a different service
Cause:The aud claim is not being checked. Any token your identity provider signed is accepted everywhere.
Fix:Verify aud against this service’s own identifier, and iss against the expected issuer. Signature validity alone is not authorisation.
Revoked user still has access
Cause:JWTs are stateless by design — nothing consults a database at verification time, so a token remains valid until it expires no matter what happens to the account.
Fix:Keep access tokens short-lived (5–15 minutes) and pair them with a refresh token that is checked against server state. For immediate revocation you need a deny-list keyed on jti, which reintroduces the state JWTs were meant to avoid — worth doing consciously rather than by accident.
kid parameter used to load a key by path
Cause:The kid header names which key to verify with. Treating it as a filename or a SQL value makes it a path-traversal or injection vector controlled entirely by the attacker.
Fix:Look kid up in a fixed allow-list of known key identifiers. Never interpolate it into a path or a query.
The three parts
A JWT is three Base64url segments joined by dots: header.payload.signature. Decoding the first two needs no key at all.
// Base64url differs from Base64: - for +, _ for /, no padding
function decodeSegment(segment) {
const b64 = segment.replace(/-/g, '+').replace(/_/g, '/');
const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, '=');
return JSON.parse(atob(padded));
}
const [header, payload] = token.split('.').map(decodeSegment);
// This is decoding, NOT verification. It proves nothing about
// the token's authenticity. Never make an authorisation
// decision from a decoded-but-unverified payload.import jwt from 'jsonwebtoken';
const claims = jwt.verify(token, publicKey, {
algorithms: ['RS256'], // pinned — never read from the token
issuer: 'https://auth.example.com',
audience: 'https://api.example.com',
clockTolerance: 5 // seconds, for minor clock skew
});import jwt # PyJWT
claims = jwt.decode(
token,
public_key,
algorithms=["RS256"], # pinned
issuer="https://auth.example.com",
audience="https://api.example.com",
)About
The JWT Tool decodes any JSON Web Token to reveal its header (algorithm, type) and payload claims (subject, name, issued-at, expiry) without needing the secret — perfect for debugging auth flows. It automatically detects token expiry and flags expired tokens with a warning. In Encode mode, you can sign a JSON payload as a new JWT using HMAC-SHA-256, SHA-384, or SHA-512 and a custom secret key. All processing runs entirely in your browser using the Web Crypto API — your tokens, payloads, and secrets are never transmitted to any server. No sign-up required.
How to use
- 1 Paste your JWT string into the Decode tab to inspect the header and payload.
- 2 Switch to the Encode tab to create a new token.
- 3 Fill in the payload JSON and enter your signing secret.
- 4 Choose the algorithm (HS256, HS384, or HS512) and click Sign.
- Can I decode a JWT without knowing the secret?
- Yes. The header and payload of a JWT are Base64URL-encoded, not encrypted, so they can be decoded and read without the signing secret. Decoding does not verify the signature — it simply lets you inspect the claims. Never put sensitive data in a JWT payload.
- Is it safe to paste my JWT into this tool?
- All decoding and encoding runs entirely in your browser — your token is never sent to any server. That said, treat production tokens with care; use test or expired tokens when debugging in any online tool.
- What signing algorithms are supported for encoding?
- The encoder supports HS256 (HMAC-SHA-256), HS384 (HMAC-SHA-384), and HS512 (HMAC-SHA-512) — the most common symmetric signing algorithms. RSA and ECDSA (RS256, ES256) are asymmetric and require a private key, which is outside the scope of this browser-based tool.
The full guide
More in Encoders & Decoders
See all encoders & decoders.