JWT decoder
Paste a token to read its header and claims, see exp, nbf and iat as dates in the time zone you choose, and get warnings about risky headers such as alg none. Decoding is not verification; you can optionally check an HMAC signature with a secret you supply. It all runs in your browser.
Do not paste production tokens, here or anywhere you do not control. A live token is a password: whoever holds it can act as you until it expires. This page decodes inside your browser and sends nothing, but browser extensions, shared screens, clipboard managers and browser history are outside this page's control. Use expired, test or staging tokens.
Decoding is not verification. Anyone can read a JWT and anyone can write one. Seeing claims here does not mean the token is genuine.
Token parts
Warnings and notes
Header
Payload
Signature
Claims explained
| Claim | Value | What it means |
|---|
Check an HMAC signature (HS256, HS384, HS512) with your own secret
This recomputes the signature in your browser with the Web Crypto API and compares it. The secret is used once, never stored, never put in a link. Do not type a production secret.
Runs in your browser. Nothing is uploaded, stored or put in a link: this page has no share link and no history on purpose. Only your time-zone choice is remembered in this browser.
How to use
- Use an expired, test or staging token. Do not paste a production token or a real signing secret.
- Paste the token. A leading
Bearer, surrounding quotes and line breaks are removed with a note. The three parts are coloured: header, payload, signature. - Read the warnings first. They are ordered danger, warning, note, and each cites the RFC section it comes from.
- In Claims explained, pick a time zone (your browser's is the default). Times are shown in that zone and in UTC, with how long ago or from now. The line above the table checks exp and nbf against your device clock.
- To check the signature of an HS256, HS384 or HS512 token, open the verification section, type the secret and say how it is written (text, Base64URL, Base64 or hex). Set Accept only algorithm to the algorithm you expect, which is what a real verifier must do.
Tokens with five parts are encrypted (JWE). Only the header can be read without the key, and the page says so.
Worked examples
The tokens below are published test values from the RFCs, so they are safe to paste. The "Load" buttons in the tool fill them in.
RFC 7515 Appendix A.1: an HS256 token
The token (also used in RFC 7519 section 3.1) is:
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
The header decodes to {"typ":"JWT", then a CRLF and "alg":"HS256"}; the RFC lists the exact bytes, including the line break, and a test compares them. The payload is {"iss":"joe", "exp":1300819380, "http://example.com/is_root":true} (laid out over three lines). The claim exp of 1300819380 is 2011-03-22T18:43:00Z. In Asia/Jakarta (UTC+07:00) that is 2011-03-23 01:43:00; in America/New_York, which was already on daylight time (UTC-04:00), it is 2011-03-22 14:43:00; in Europe/London, still on GMT, it is 18:43:00. Because the date is in 2011, the verdict line says the token expired. Check the signature with the key from the RFC (button "Fill the RFC 7515 key", Base64URL): the result is VALID. Change one character of the payload and it becomes INVALID.
RFC 7519 section 6.1: an unsecured token
eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.
The header is {"alg":"none"}, and the signature part is empty, so the token ends with a dot. The tool shows a Danger note: the token has no signature, so anyone could have written it. Verification reports "Cannot check" because there is nothing to check.
A made-up HS256 token
Button "Load an HS256 test token": claims {"sub":"1234567890","name":"John Doe","iat":1516239022}; iat is 2018-01-18T01:30:22Z. Secret your-256-bit-secret gives VALID; a secret with one letter changed gives INVALID. We generated this token with Python's hmac module, independent of this page's code. The 19-byte secret is shorter than the 32 bytes RFC 7518 section 3.2 requires for HS256, and the tool says so.
What goes wrong
Inputs that fail to decode, with the tool's exact message:
Pasting something that is not a JWT
Input: SGVsbG8gV29ybGQ=
Message: This does not look like a JWT: there is no "." in it.
That string is plain Base64 for "Hello World"; use the Base64 tool for it.
A token cut off before the signature
Input: eyJhbGciOiJub25lIn0.e30
Message: Found 2 dot-separated parts. A signed JWT (JWS) has 3 and an encrypted JWT (JWE) has 5.
Copying from a log line or a terminal often drops the end. Even an unsecured token needs the trailing dot: "header.payload." with an empty third part (RFC 7519 section 6.1).
A "+" in a token part
Input: eyJhbGciOiJIUzI1NiJ9.e30.abc+def
Message: The signature (part 3) is not valid Base64URL: Invalid character "+" (U+002B) at position 4.
JWT parts use - and _, never + and /. A "+" usually means the token went through a URL query string, where + is turned into a space, or it was re-encoded as ordinary Base64.
A header that is valid Base64URL but not JSON
Input: aGVsbG8.e30.
Message: The header is not valid JSON: Unexpected "h", expected a value (line 1, column 1)
"aGVsbG8" is Base64URL for the text "hello". The decoder reports the first byte of the decoded text that breaks the JSON grammar.
A hand-written header with an unquoted key
Input: e2FsZzoiSFMyNTYifQ.e30.
Message: The header is not valid JSON: Unexpected "a", expected a double-quoted key (line 1, column 2)
The header decodes to {alg:"HS256"}. JavaScript object syntax is not JSON.
A time claim in milliseconds
A payload of {"exp":1767225600000} decodes without error, but the date is far in the future (year 57971 in UTC). The claims table adds: "This looks like milliseconds, not seconds. NumericDate counts seconds (RFC 7519 section 2)..." A library that reads the value as seconds sees a token that stays valid for millennia; whether a given library rejects such a value is up to its implementation and is not specified by the RFC.
A token that claims to be signed but is not
A header of {"alg":"HS256"} followed by an empty signature part is flagged as Danger: "alg is "HS256" but the signature part is empty." A correct verifier rejects it. A library that treated an empty signature as "nothing to check" would be making the kind of mistake RFC 8725 section 2.1 describes for "none".
A duplicated claim
A payload of {"sub":"alice","sub":"admin"} is valid JSON text, but RFC 7519 section 4 says parsers must either reject it or use the last value. Different libraries pick differently, so a check on the first value and a use of the last is an authorization bug. The tool shows the last value ("admin") and warns.
Limits & gotchas
- Decoding is not verification. Without a key you know only what the token claims, not who made it. Even a valid HMAC check proves only that someone with the secret signed these exact bytes. It says nothing about exp, nbf, iss, aud or whether the algorithm is acceptable to your application; a real verifier checks all of them (RFC 7519 section 7.2, RFC 8725).
- Only HS256, HS384 and HS512 can be checked here. RS*, PS* and ES* tokens need a public key in a format this page does not parse, and verification for them is not implemented. Their payloads still decode.
- The time verdict uses your device clock with no leeway. RFC 7519 allows verifiers a few minutes of leeway. A token that expired 30 seconds ago may still be accepted by a server that allows leeway.
- Decrypting is not supported. A five-part JWE shows its header only.
- Large integers. JSON numbers beyond 253 are shown exactly in the pretty-printed payload but time maths treats claims as doubles. A warning appears when a number cannot be held exactly.
- Findings are generic. They come from the header and claims. They do not know how your server is configured, so "no exp claim" is a note, not a defect: the RFC makes exp optional.
- No share link, no history. Tokens are credentials, so this page never writes them to the address bar, local storage or the clipboard unprompted. Only your time-zone choice is remembered in this browser.
- Time zones come from your browser's tz database. Dates before 1970 and the far future depend on it.
- The secret field is a password input and is cleared with the Clear button. Browsers and password managers may still offer to save it; do not use real secrets.
FAQ
Is decoding a JWT the same as verifying it?
No. A JWT's header and payload are only Base64URL-encoded JSON, so anyone can read them and anyone can write a token with any claims. Only checking the signature (or decrypting, for a JWE) with the right key shows that the issuer really produced the token. RFC 7519 section 7.2 lists validation as separate steps from decoding, and this page says "decoded" for what it does without a key.
Why did my "expired" token still work (or a fresh one fail)?
The exp claim is only meaningful if the verifier checks it, and it is checked against the verifier's clock. RFC 7519 section 4.1.4 says the current time must be before exp and allows "some small leeway, usually no more than a few minutes" for clock skew. The verdict on this page uses your device clock and no leeway; if your clock is wrong the verdict is wrong, and the server may disagree with it.
The date looks like it is in the year 57971. What happened?
JWT time claims are NumericDate values: seconds since 1970-01-01T00:00:00Z (RFC 7519 section 2). JavaScript's Date.now() returns milliseconds. A token created with 1767225600000 instead of 1767225600 has an exp thousands of years away. The decoder flags any time claim at or above 1e11 as probably milliseconds.
What is the "alg: none" problem?
"none" is a registered algorithm for tokens with no signature at all (RFC 7519 section 6, RFC 7518 section 3.6). RFC 8725 section 2.1 describes attacks where libraries accepted a token whose header an attacker had changed to "none" and skipped the signature check. A verifier should only accept the algorithms the application chooses, and RFC 8725 section 3.2 says libraries must not accept "none" unless the caller asked for it.
What is HS256/RS256 key confusion?
RS256 verifies with a public key, which is not secret. If a server chooses how to verify from the token's own "alg" header, an attacker can change it to HS256 and sign the token with HMAC using that public key as the secret; a vulnerable library then "verifies" it. RFC 8725 section 2.1 describes the attack and section 3.1 the fix: the library must be told which algorithm to expect for each key and must refuse any other. This page shows a warning for asymmetric algorithms for this reason, and its optional check can be pinned to one HMAC algorithm.
Sources
- IETF: RFC 7519: JSON Web Token (JWT) Used for: Registered claims iss, sub, aud, exp, nbf, iat, jti (4.1); exp/nbf semantics and clock-skew leeway; NumericDate; the typ header; the Unsecured JWT with alg "none" (6, 6.1); the JWT example in section 3.1.
- IETF: RFC 7515: JSON Web Signature (JWS), Appendix A.1 Used for: The HS256 example: header, payload, key, signing input and signature (A.1).
- IETF: RFC 7518: JSON Web Algorithms (JWA) Used for: HS256/384/512 use HMAC with SHA-256/384/512 (3.2) and need a key at least as long as the hash output (3.2); the "none" algorithm (3.6); algorithm names.
- IETF: RFC 8725: JSON Web Token Best Current Practices Used for: Algorithm verification, "none" handling and the risk of attackers changing the algorithm.
- IETF: RFC 4648: The Base16, Base32, and Base64 Data Encodings Used for: The two Base64 alphabets (tables 1 and 2), padding (3.2), rejecting non-alphabet characters (3.3), canonical encoding and zero pad bits (3.5), no line feeds unless a referring spec says so (3.1), the "base64url" name (5), the test vectors (10), the "=" percent-encoding remark (5).
- IETF: RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format Used for: JSON grammar: no comments, no trailing commas, double-quoted strings, number syntax; duplicate names are "unpredictable".
- W3C: Web Cryptography API (SubtleCrypto: digest, importKey, sign, verify) Used for: digest() supports SHA-1, SHA-256, SHA-384, SHA-512; HMAC sign/verify with importKey. MD5 is not in the list.
Every document above was opened and read on 2026-10-02. Documentation changes; if a page here disagrees with the current docs, trust the docs and tell us.