Tools
JWT Decoder
Paste a JSON Web Token to inspect its header, payload, and signature. Expiration (exp) and not-before (nbf) claims are highlighted automatically. Signature verification requires the issuer's key, so it is intentionally not performed here.
Header
{
"alg": "HS256",
"typ": "JWT"
}Payload
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516325422
}- iat
- 1/18/2018, 1:30:22 AM
- exp
- 1/19/2018, 1:30:22 AM
Signature
Signature verification requires the secret key and is not performed in the browser.
About the JWT decoder
JWT Decoder splits a JSON Web Token into its three dot-separated parts — header, payload, and signature — and renders the header and payload as formatted JSON. It updates as you type, with no Submit button: paste a token and the decoded panels, an expiry badge, and any iat, nbf, or exp claims (shown as local dates) appear immediately. Malformed input — the wrong number of segments, unparsable base64, invalid JSON — surfaces as an inline error instead of a blank panel.
Decoding runs entirely in your browser — the token is never sent to a server. The code splits the token on its . characters, then base64url-decodes the header and payload: it swaps JWT's URL-safe alphabet (-/_) back to standard base64 (+//), restores the padding atob expects, and pipes the result through TextDecoder before JSON.parse, so multi-byte UTF-8 — an accented name, an emoji in a sub claim — survives the round trip. The signature segment is left as raw base64url text; it's opaque bytes, not JSON, so there's nothing to decode.
Reach for this when you're debugging an Authorization: Bearer header, inspecting a token issued by an OAuth or OIDC provider, or checking whether a claim (exp, roles, scope) matches what your API expects — all without writing a script or pasting a production token into a third-party site. Decoding is not the same as verifying: this tool never checks the signature, since that requires the issuer's secret or public key. A token can decode cleanly here and still be expired, tampered with, or signed by the wrong party.
Examples
Input (the demo token the widget preloads by default):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYzMjU0MjJ9.demo-signature
Header:
{ "alg": "HS256", "typ": "JWT" }
Payload:
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "exp": 1516325422 }exp (1516325422 → January 2018) is long past, so the widget's expiry badge reads 'Expired' as soon as this token loads — it's the tool's own preloaded demo, chosen to show that state on first paint. Edit any character above and every panel updates immediately; there's no Decode button.
function base64UrlDecode(segment) {
const b64 = segment.replace(/-/g, '+').replace(/_/g, '/')
const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4)
return atob(padded)
}
const [, payloadB64] = token.split('.')
const payload = JSON.parse(base64UrlDecode(payloadB64))
// → { sub: '1234567890', name: 'John Doe', iat: 1516239022, exp: 1516325422 }This mirrors decodeJwt internally: swap the URL-safe alphabet back to standard base64, restore the padding atob requires, decode, then JSON.parse. The real implementation adds a TextDecoder('utf-8') pass before parsing so non-ASCII claims survive — omitted here since this payload is plain ASCII.
<?php
// $jwt holds the raw token string
[$header, $payload, $signature] = explode('.', $jwt);
$claims = json_decode(
base64_decode(strtr($payload, '-_', '+/')),
true
);
// → ['sub' => '1234567890', 'name' => 'John Doe', 'iat' => 1516239022, 'exp' => 1516325422]strtr swaps the URL-safe alphabet back the same way the JS version does. Unlike atob, PHP's base64_decode doesn't reject a missing '=' padding, so there's no manual re-padding step.
Frequently asked questions
Does decoding a JWT verify its signature?
No. This tool only decodes the base64url-encoded header and payload — it never checks the signature. Verifying requires the key that produced the signature (a shared secret for HS256, or the issuer's public key for RS256/ES256), and that check belongs wherever the key actually lives, not in a client-side decoding tool.
Is it safe to paste a real JWT into this tool?
Yes. Decoding runs entirely in your browser — the token is split, base64url-decoded, and parsed locally with atob and JSON.parse. It is never transmitted to a server, logged, or stored anywhere.
Are JWTs encrypted?
Not by default — a standard JWT (technically a JWS) is base64url-encoded, not encrypted, so anyone holding the token can decode its header and payload exactly as this tool does. The signature only proves the token hasn't been altered since it was signed; it does nothing to hide the contents. Encrypt sensitive claims yourself, or use JWE, if they need to stay confidential.
What do the exp, iat, and nbf claims mean?
All three are Unix timestamps in seconds, not milliseconds: iat (issued at) is when the token was created, exp (expiration) is when it stops being valid, and nbf (not before) is the earliest moment it becomes valid. This tool converts each to a local date and, based on exp and nbf, shows a 'Valid', 'Expired', or 'Not yet valid' badge.
Why do I get an 'Invalid JWT' error?
It means the pasted text doesn't have the three period-separated segments a JWT requires — a truncated token, stray whitespace, or a non-JWT string (like a plain API key) will all trigger it. If the segment count is right but you still get an error, one segment's content isn't valid base64 or doesn't contain valid JSON once decoded.