Security & devFree

JWT Decoder

Free JWT decoder. Decode header and payload from a JSON Web Token client-side. Does not verify signatures.

Last updated 7 October 2026

Header


Payload

Decoding is not verifying. The two panels above show what a token says, with no check that anybody authorised it to say so. A decoder cannot distinguish a genuine token from one an attacker wrote by hand ten seconds ago, and this page does not try. Treat everything it displays as a claim of unknown origin. Every quotation below was read from the RFC named, on 2 October 2026.

The one sentence that matters, from the specification itself

RFC 7519, which defines the JSON Web Token, puts it in section 11.1 under the heading Trust Decisions:

RFC 7519, section 11.1

on relying on claims“The contents of a JWT cannot be relied upon in a trust decision unless its contents have been cryptographically secured and bound to the context necessary for the trust decision.”

Nothing above has been cryptographically secured. The page splits your token on its dots, base64url decodes the first two pieces, parses them as JSON and prints them. It reads the third piece only to count it and, when the header says alg is none, to notice whether it is empty; it never verifies it, because verifying needs a key. It has no key, makes no network request, and performs no cryptographic operation of any kind. The status line says “Decoded (signature not verified)” because that is the whole truth of what happened, and it is the reason the line is amber rather than green.

That sounds pedantic until you notice how often a decoder stands in for a verifier. A support engineer sees “role”: “admin” in a decoder and treats it as evidence the holder is an administrator; a developer reads “iss” and concludes the token came from that issuer. In both cases the only thing established is that somebody base64url encoded some JSON. Anyone can. You can do it in fifteen seconds with the Base64 tool on this site and produce a token this page displays exactly as it displays a real one.

Four tokens this page cannot tell apart

The clearest way to see the limit is to look at inputs that differ in the way that matters most and produce the same screen. Each of these was run through this page's own decoding function while writing this section.

What the page reports · measured 7 October 2026 against the page’s own code

a properly signed HS256 tokenheader and payload shown · “Decoded (signature not verified)”
the same token, payload editedheader and payload shown · “Decoded (signature not verified)”
alg changed to “none”, signature emptiedheader and payload shown · “Decoded (signature not verified). The header says alg “none”, so this is an Unsecured JWT…”
signature replaced with 22 letter Asheader and payload shown · “Decoded (signature not verified)”

Three of those four states produce one output. The second row is a forgery that any verifier rejects instantly, because changing a single character of the payload invalidates the MAC computed over it. The fourth is noise. A decoder has no way to separate those from the first, because separating them requires the key, and no amount of care in this page will change that.

The third row is the exception, and only since 7 October 2026. Until then it read exactly like the other three. The page now names an alg of none in the status line, because that is the one case where “signature not verified” understates what you are looking at: there is no signature, so there is nothing a key would verify. That is a label on a shape, not a verdict on a token, and it does nothing for rows one, two and four.

So what would it take? Four things, in this order, none of which is available to a page in your browser.

A key you already trust. Not a key named in the token. RFC 8725 section 3.10 is specifically about this: “blindly following a ‘jku’ (JWK set URL) or ‘x5u’ (X.509 URL) header, which may contain an arbitrary URL, could result in server-side request forgery (SSRF) attacks”, and a kid taken from a token and fed into a lookup can carry an injection. The key has to come from your own configuration or from an endpoint you decided to trust before the token arrived.

A decision about algorithms, made in advance. RFC 8725 section 3.1: “Libraries MUST enable the caller to specify a supported set of algorithms and MUST NOT use any other algorithms when performing cryptographic operations. The library MUST ensure that the ‘alg’ or ‘enc’ header specifies the same algorithm that is used for the cryptographic operation.” Reading the algorithm out of the token and then using it is the defect, not the procedure.

The cryptographic check itself, recomputed over the first two segments and the dot between them, as RFC 7515 defines the signing input.

Then the claims. exp and nbf against your clock, iss against the issuer you expected, aud against your own identity. RFC 7519 section 7.2 closes on the point that even a valid signature is not sufficient: “Even if a JWT can be successfully validated, unless the algorithms used in the JWT are acceptable to the application, it SHOULD reject the JWT.”

A browser page can do none of this, which is why no honest JWT page in a browser is a verifier. Verification belongs in the service that is about to act on the token.

Signed is not encrypted, and the payload is readable by anyone holding it

This is the second misconception, and it is at least as expensive as the first. RFC 7519's abstract describes the two available shapes: the claims are “used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.”

There is a test for which one you are holding, and you just ran it: if the payload appeared in the panel above, yours is the signed kind. Signing protects a token from being changed. It protects nothing from being read. The segments are base64url, which is a character set rather than a cipher, so the payload is as readable as the URL it travelled in. Anyone holding the token can see every claim in it, including the browser it was delivered to, any extension running in that browser, any proxy that logged the request, and this page.

RFC 7519 section 12, Privacy Considerations, is unusually practical about the options:

RFC 7519, section 12

the obligation“A JWT may contain privacy-sensitive information. When this is the case, measures MUST be taken to prevent disclosure of this information to unintended parties.”
option one“use an encrypted JWT and authenticate the recipient”
option twotransmit only “using protocols utilizing encryption that support endpoint authentication, such as Transport Layer Security (TLS)”
option three“Omitting privacy-sensitive information from a JWT is the simplest way of minimizing privacy issues.”

The third option is the one that gets forgotten in design reviews. A token is a thing that sits in browser storage, gets copied into bug reports and travels through logs. An email address, an internal user identifier, a role list, a phone number or an employment status in the payload is in all of those places. Over TLS it is protected in transit and nowhere else.

It is worth being clear about what the signature does buy, because it is not nothing. A signed token cannot be altered without detection by a service that checks it, which is what lets a service accept claims it did not itself look up. That is the entire value proposition, and it is a good one. It just has no confidentiality in it.

The token this page ships in the box

The textarea above is pre-filled so that the page shows something on arrival. Since 7 October 2026 the value in it is the example JWT from RFC 7519 section 3.1, taken from the published text rather than retyped, so what you meet first is a genuine HS256 token with a real MAC over its own header and payload:

The default sample, decoded

header{“typ”:“JWT”,“alg”:“HS256”}
payload{“iss”:“joe”,“exp”:1300819380,“http://example.com/is_root”:true}
third segmentdBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk, a real HMAC SHA-256 value
exp 130081938022 March 2011 18:43:00 UTC, long expired, and this page still will not tell you so

What used to be here, and why it was replaced

Until 7 October 2026 the box held {“alg”:“none”,“typ”:“JWT”} over a payload naming Ada Lovelace, with the three literal characters sig as its third segment, and the page reported it exactly as it reported a genuine signed token. So the first thing every visitor saw was the most attacked token shape in the specification, modelled as normal. It was not even a conforming example of that shape, and the definition is the reason.

RFC 7519 section 6 defines the category: “An Unsecured JWT is a JWS using the ‘alg’ Header Parameter value ‘none’ and with the empty string for its JWS Signature value.” RFC 7515's terminology section is blunter: an Unsecured JWS is “a JWS that provides no integrity protection”. Both permit the form; RFC 7519 explains why it exists, for cases “in which the JWT content is secured by a means other than a signature and/or encryption contained within the JWT”.

Section 6 requires the empty string as the signature, and that sample carried sig; RFC 7519's own example in section 6.1 ends with a dot and nothing after it. Paste such a token here today and the status line says exactly that, rather than printing the panes and leaving you to notice.

RFC 8725, the Best Current Practice document published in 2020 after what its introduction calls “several widely published attacks on implementations and deployments”, puts it first on its list of threats in section 2.1: “The algorithm can be changed to ‘none’ by an attacker, and some libraries would trust this value and ‘validate’ the JWT without checking any signature.” The same section records the companion trick, which is to change RS256 to HS256 so that a library “would try to validate the signature using HMAC-SHA256 and using the RSA public key as the HMAC shared secret”, catalogued as CVE-2015-9235. Both attacks work by making a verifier take the token's word about how to check the token.

And there is a real, legitimate use for none, which is why it was not simply removed. RFC 8725 section 3.2: “if a JWT is cryptographically protected end-to-end by a transport layer, such as TLS using cryptographically current algorithms, there may be no need to apply another layer of cryptographic protections to the JWT. In such cases, the use of the ‘none’ algorithm can be perfectly acceptable. The ‘none’ algorithm should only be used when the JWT is cryptographically protected by other means.” The rule for a library is then stated twice, once for each direction: “JWT libraries SHOULD NOT generate JWTs using ‘none’ unless explicitly requested to do so by the caller. Similarly, JWT libraries SHOULD NOT consume JWTs using ‘none’ unless explicitly requested by the caller.”

If you are testing your own service, the useful experiment follows from that sentence. Take a working token, change its header to {“alg”:“none”}, delete the signature, and send it. A service that accepts it has the defect RFC 8725 section 2.1 describes, and you have found it in a minute.

Reading exp, iat and nbf without a calculator

The panes above print timestamps as the integers they are, because that is what the token contains. RFC 7519 section 2 defines the type: a NumericDate is “a JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds”, equivalent to the POSIX definition of Seconds Since the Epoch “in which each day is accounted for by exactly 86400 seconds”.

Three claims use it, and each has a different rule attached. Section 4.1.4: exp “identifies the expiration time on or after which the JWT MUST NOT be accepted for processing”. Section 4.1.5: nbf “identifies the time before which the JWT MUST NOT be accepted for processing”. Both permit “some small leeway, usually no more than a few minutes, to account for clock skew”, and both are optional. Section 4.1.6's iat records when the token was issued and carries no acceptance rule at all.

Reference points for reading a NumericDate by eye

17908992002 October 2026 00:00:00 UTC · anything smaller is in the past
151623902218 January 2018 · the iat in the sample this page shipped until 7 October 2026
130081938022 March 2011 · the exp in RFC 7519's own example
214748364719 January 2038 · the signed 32-bit limit
3600 / 86400 / 604800one hour / one day / one week, in seconds

Those are enough to read a token's lifetime off a pair of claims. Subtract iat from exp: 300 is five minutes, 3600 an hour, 86400 a day, 2592000 thirty days. A long gap on an access token is worth a question, because that number is the window in which a stolen one is useful.

This page does not check any of them. A token that expired in 2001 decodes with the same amber “Decoded (signature not verified)” as one minted a second ago. Expiry is a claim like any other, and like any other it means nothing until the signature has been verified: an attacker editing a payload would change exp first.

What this page refuses, and what it used to accept

Until 7 October 2026 four inputs that the specifications exclude got through and were printed under the same “Decoded” status as a real token. A decoder that shows you a “payload” which cannot be a payload is worse than one that refuses, because the display reads as confirmation that the token is well formed. All four are now refused, and each refusal names the rule rather than failing generically. They are listed here because the old behaviour is what a reader may remember, and because the rules are worth knowing either way.

A token with no signature segment. RFC 7515 defines the compact serialization as three parts: “BASE64URL(UTF8(JWS Protected Header)) || ‘.’ || BASE64URL(JWS Payload) || ‘.’ || BASE64URL(JWS Signature)”. The page used to require only two, so header.payload decoded and was reported as decoded. It now requires exactly three and says how many it found. Five parts is the shape of an encrypted token, and is named as such.

A header with no algorithm. RFC 7515 section 4.1.1 on alg: “This Header Parameter MUST be present and MUST be understood and processed by implementations.” Any JSON object in the first segment used to be displayed as a header, with or without it. A header lacking alg is now refused, quoting that sentence.

A payload that is not a JSON object. RFC 7519 section 7.2, at steps 4 and 10, requires “a completely valid JSON object”. An array, a bare number and a quoted string were all accepted and printed, and so was a header of the same shapes, which the original note missed. Both parts are now checked against their own step, and the refusal says which kind of JSON arrived instead.

A token with whitespace inside a segment. The same section requires base64url decoding “following the restriction that no line breaks, whitespace, or other additional characters have been used”. The browser's own decoder removes ASCII whitespace before it begins, as the WHATWG Infra Standard specifies, so a token that wrapped across lines in a terminal used to decode here anyway: convenient, and not what the specification describes. The page now checks each segment for whitespace before handing it to the browser, because the browser will not. Rejoin the lines and it decodes.

A value pasted straight out of an Authorization header. Not a leniency but the opposite, and the commonest paste mistake there is. A leading Bearer used to produce the browser's own words, “Failed to execute ‘atob’ on ‘Window’: The string to be decoded is not correctly encoded”, which names nothing a reader can act on. The prefix is now dropped for you and the status line says it was dropped, so the fix is visible rather than silent.

One further item is a limit rather than a leniency: encrypted tokens are not supported. Decrypting a JWE needs the recipient's private key, which is not something to paste into a web page. A five-part token is now recognised as that shape and refused by name instead of failing on a JSON error.

What this page does not know

One text box. Everything below is outside what a decoder can establish, and most of it is outside what any decoder can establish.

Whether the signature is valid. The third segment is never read. This is not a limitation to be fixed later; it is the definition of a decoder.

Who issued the token. iss is a string in the payload. Unverified, it is a string somebody typed.

Whether the token has expired or been revoked. No clock comparison is made, and revocation is not visible in a token at all. A signed, unexpired token that an administrator cancelled five minutes ago looks identical here to a live one.

Whether the claims are true. role, scope, admin, tenant: every one is an assertion whose only backing is the signature nobody checked.

Whether the algorithm is acceptable. The header is displayed, not judged. RFC 8725 section 3.2 requires an application to “only allow the use of cryptographically current algorithms that meet the security requirements of the application”, and that list lives in your service's configuration, not in the token.

Whether the token is the right kind. RFC 8725 section 3.11 recommends explicit typing through the typ header precisely because “sometimes, one kind of JWT can be confused for another”. An ID token used where an access token was expected may decode perfectly and be entirely wrong.

Whether pasting it here was wise. The decoding is local: this page's script splits the string, checks its shape and calls the browser's built-in atob, with no network request of any kind, which you can confirm in the page source or a network tab. That is a narrower promise than it sounds, because a token you paste anywhere is a token you have handled. Prefer expired or test tokens, and rotate anything live that you have copied.

Sources

Every quotation above was read from the IETF or WHATWG document named, from the published text rather than from a summary: on 2 October 2026, and re-read on 7 October 2026 when the decoder was changed to enforce them. The four-token comparison, the behaviour of the shipped sample, the refused inputs and the timestamp conversions were produced by running this page's own decoding function and this page's own arithmetic. Part of the QuikUtil tools collection; the Base64 Encode & Decode tool is what the three segments are written in, and the JSON Formatter is useful on a payload large enough to be hard to read.

Frequently asked questions

Does this page verify the signature?

No. It splits the token on the dots, base64url decodes the first two parts and prints them. It never looks at the third part, never fetches a key and never performs a cryptographic operation, which is why the status line says “signature not verified” rather than “valid”. RFC 7519 section 11.1 states the consequence: “The contents of a JWT cannot be relied upon in a trust decision unless its contents have been cryptographically secured and bound to the context necessary for the trust decision.” Nothing shown above has been cryptographically secured by this page, so nothing shown above is evidence of anything.

What would verifying actually require?

Four things, none of them available here: a key you already trust rather than one named in the token; a list of acceptable algorithms fixed before the token arrives, because RFC 8725 section 3.1 says libraries “MUST enable the caller to specify a supported set of algorithms and MUST NOT use any other algorithms”; the signature recomputed over the first two segments; and then exp, nbf, iss and aud checked against your own clock and identity.

The token in the box decodes fine. Does that mean it is valid?

No. Since 7 October 2026 the page does check the SHAPE, and refuses a token that is not three parts, a header without alg, and a header or payload that is not a JSON object. None of that is validity. Decoding tells you a string was base64url and held JSON objects of roughly the right form; it tells you nothing about whether the signature is genuine, who issued the token, or whether it has expired. Until that date the box shipped a sample whose header was {“alg”:“none”,“typ”:“JWT”} with the literal three letters sig as its signature, reported exactly as a genuine signed token, which was a good demonstration of the same point.

Is a JWT encrypted? Can anyone read what is inside mine?

A signed JWT is not encrypted, and anyone holding the token can read all of it. RFC 7519 describes the two options: the claims are carried either “as the payload of a JSON Web Signature (JWS) structure” or “as the plaintext of a JSON Web Encryption (JWE) structure”. There is a simple test for which you have: if the payload appears in the panel above, it is the signed, readable kind. Base64url is a transport encoding, not a wrapper. RFC 7519 section 12 is direct about the design consequence: “Omitting privacy-sensitive information from a JWT is the simplest way of minimizing privacy issues.”

What does alg “none” mean, and is it dangerous?

It means the token carries no integrity protection at all. RFC 7515 defines an Unsecured JWS as “a JWS that provides no integrity protection” that uses “the ‘alg’ value ‘none’”. It is dangerous in one specific way, described in RFC 8725 section 2.1: “The algorithm can be changed to ‘none’ by an attacker, and some libraries would trust this value and ‘validate’ the JWT without checking any signature.” The same document records the related trick of switching RS256 to HS256 so a library verifies with the public key as the shared secret, which is CVE-2015-9235. There is a legitimate use, where the token is already protected by something else: “The ‘none’ algorithm should only be used when the JWT is cryptographically protected by other means.” RFC 8725 section 3.2 then draws the line for implementers: “JWT libraries SHOULD NOT consume JWTs using ‘none’ unless explicitly requested by the caller.”

My exp is 1790899200. Has the token expired?

That one expires on 2 October 2026 at 00:00:00 UTC. RFC 7519 section 2 defines the format: a NumericDate is “the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds”, with every day counted as exactly 86,400 seconds. This page prints the integer and does not compare it with anything, so it cannot tell you whether a token has expired. Section 4.1.4 says what the number means: exp “identifies the expiration time on or after which the JWT MUST NOT be accepted for processing”, with the caveat that implementers “MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew”.

Can I paste a production token in here?

The decoding is local. A short script on this page splits the string, checks its shape and calls the browser's own atob; there is no fetch, no form submission and no server involved, which you can confirm from the page source or a network tab. That is a narrower promise than it sounds, though. Pasting a live token anywhere puts it in your clipboard, in this browser's memory and possibly in an operating-system clipboard history, and whatever brought it to you may have put it in a shell history or a log. The sound habit is to use an expired token or a test one for inspection, and to rotate any live credential you have handled.

Why does my token not decode when it looks fine?

Three causes cover most of it, and two of them now explain themselves. A “Bearer ” prefix left on the front is dropped for you, and the status line says so; until 7 October 2026 it produced the browser’s own “The string to be decoded is not correctly encoded” and no hint. An encrypted JWE, which has five segments rather than three, is named as an encrypted token rather than failing on a JSON error. A truncated copy still gives an error, now saying which part would not decode. JWE is not supported here.

Does this page check that the thing I pasted is a JWT at all?

Yes, as of 7 October 2026, against four rules, and it names the one that failed. Exactly three dot-separated parts, because RFC 7515 section 7.1 defines the compact serialization that way. An alg in the header, which RFC 7515 section 4.1.1 says “MUST be present”. A header and a payload that are both JSON objects, which RFC 7519 section 7.2 requires at steps 4 and 10. And no whitespace inside a segment, which the same section forbids and the browser’s own decoder would otherwise strip. Before that date it required only two segments and checked none of the rest. Read the panes as a view of what you pasted and of its shape, still not a verdict on it: nothing here checks the signature.

Related tools