How to decode a JWT
- Paste the token into Token. A whole
Authorization: Bearer eyJ…line works, and so do surrounding quotes or a token wrapped over several lines. - Read the result. The three parts are shown in different colors, the header and payload are pretty-printed, and Claims lists every claim with its meaning. Times such as
expandiatappear as local time, UTC and “expires in 1 hour” style relative time. - To check the signature, paste the shared secret or the public key under Verify signature. The field label follows the token’s
alg: Secret for HS256/384/512, Public key for everything else. - Read the warnings. The tool flags
alg: none, expired tokens, annbfin the future, a missingexp, times written in milliseconds, and headers that point to remote keys (jku,x5u) or embed their own key (jwk).
You can also open a token directly with a link such as /jwt-decoder/#token=eyJ…. The part after # is a URL fragment, and browsers never send fragments to the server.
What is inside a token
A signed JWT (a JWS in compact form) is three Base64URL strings joined by dots: header.payload.signature. Here is a real HS256 token, issued on 1 January 2026 and valid for one hour:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VyLTQyIiwiYXVkIjoiYXBpLmV4YW1wbGUuY29tIiwiaWF0IjoxNzY3MjI1NjAwLCJleHAiOjE3NjcyMjkyMDB9.U3Gb12hN5Fnpi1rJ7nrkUzIEidkg-hPnbgiIpTah3R4
The first part decodes to the header {"alg":"HS256","typ":"JWT"}. The second is the payload:
{"iss":"https://auth.example.com","sub":"user-42","aud":"api.example.com","iat":1767225600,"exp":1767229200}
The third part is 32 bytes of HMAC-SHA256 computed over the first two parts, so changing a single character of the header or payload breaks it. Nearly every JWT starts with eyJ, the Base64 of {". The parts use the URL-safe alphabet (- and _) without = padding; How JWT uses Base64URL walks through decoding one by hand.
JWS or JWE: signed versus encrypted tokens
Most JWTs are JWS: signed, readable by anyone. A JWE is encrypted and has five parts instead of three: header.encrypted_key.iv.ciphertext.tag. Its header stays readable and names the key management algorithm (alg, for example RSA-OAEP-256 or dir) and the content cipher (enc, for example A256GCM).
Paste a JWE here and the tool decodes that header, shows alg, enc and kid, and marks the payload as encrypted. Decrypting it needs the recipient’s private key or the shared key, and this tool does not decrypt JWE. If you need a token whose payload users cannot read, JWE is the right format; putting secrets in a JWS payload is the same as putting them in plain text, as explained in Base64 is not encryption.
JWT claims reference
The first seven rows are the registered claims of RFC 7519; the rest are common in OpenID Connect and OAuth tokens. Every time value is a NumericDate: whole seconds since 1970-01-01 UTC, never milliseconds.
| Claim | Name | What to check |
|---|---|---|
iss | Issuer | Who created and signed the token. Must match the issuer you trust. |
sub | Subject | The user or entity the token is about, often a user ID. |
aud | Audience | Who the token is meant for. A string or an array; your API should reject tokens addressed to someone else. |
exp | Expiration time | After this moment the token must be rejected. |
nbf | Not before | Before this moment the token is not valid yet. |
iat | Issued at | When it was issued. Useful for age limits. |
jti | JWT ID | A unique ID, used to detect replays or revoke a single token. |
azp | Authorized party | The OAuth client the token was issued to (OpenID Connect). |
scope, scp | Scopes | Granted OAuth scopes; scp is the Microsoft and Okta spelling. |
nonce | Nonce | Ties an OpenID Connect ID token to the login request that asked for it. |
auth_time | Authentication time | When the user actually logged in, which can be long before iat. |
roles, groups | Roles, groups | Authorization data added by the identity provider. |
tid, oid | Tenant, object ID | Microsoft Entra ID tenant and user object identifiers. |
The header has its own parameters: alg (the signature algorithm), typ (usually JWT, or at+jwt for OAuth access tokens), kid (which key signed it) and cty (set to JWT when a token is nested inside another).
Verifying the signature: which key to paste
Only the signature check shows where a token came from, and the key it needs depends on the algorithm family in the header.
alg | Key to paste | Signature length |
|---|---|---|
| HS256, HS384, HS512 | The shared secret. Set Secret is to Text, Base64/Base64URL or Hex to match how it is stored; an oct JWK works too. | 32, 48, 64 bytes |
| RS256–RS512, PS256–PS512 | The RSA public key: PEM BEGIN PUBLIC KEY, PEM BEGIN RSA PUBLIC KEY, an X.509 certificate, a JWK or a JWKS. | Key size; 256 bytes for RSA-2048 |
| ES256, ES384, ES512 | The EC public key on P-256, P-384 or P-521, in any of the forms above. | 64, 96, 132 bytes (raw r‖s) |
| EdDSA | An Ed25519 public key as PEM or an OKP JWK. | 64 bytes |
For tokens from an identity provider, the public keys live at the jwks_uri listed in https://<issuer>/.well-known/openid-configuration. This page cannot fetch that URL because it cannot connect to other servers, so open it in a new tab and paste the JSON. With a JWKS the key is picked by kid; if the token has no kid, every compatible key is tried.
ECDSA signatures in DER form (usually 70 to 72 bytes for ES256 instead of 64) are detected, converted and flagged, because JWS requires raw r‖s and many libraries reject DER. A pasted private key also works (the public half is derived), though verifying never needs one. ES256K and Ed448 are not supported, as WebCrypto lacks them.
If an HS256 token fails with a secret you are sure of, the secret is often Base64-encoded somewhere in your config and the server decodes it before use. Switch Secret is to Base64 and try again.
Why decoding a token here is safe
A JWT in an Authorization header is a working credential until it expires. If you paste a live production token into a web page that sends it to a server, whoever runs that server can replay it against your API, and from the outside you usually cannot tell whether a page does that.
On this page the decoding and the WebCrypto signature check both run in your browser. The page also carries a Content Security Policy whose connect-src allows only /cdn-cgi/rum, where Cloudflare Web Analytics counts page views, so the browser blocks every other fetch, XHR, WebSocket and beacon request the page might try. You can confirm it yourself: open the developer tools on the Network tab and paste a token, or switch off Wi-Fi after the page has loaded and keep decoding.
Even so, prefer test tokens or tokens that are about to expire, and if a production token was pasted somewhere you do not control, revoke the session.
Expired tokens, nbf and clock skew
exp and nbf are checked against the verifier’s clock, and clocks drift. A token issued by a server whose clock runs 30 seconds fast can arrive with iat and nbf in the future and be rejected as “not valid yet”. JWT libraries therefore accept a small tolerance: leeway in PyJWT, clockTolerance in jsonwebtoken and jose.
This decoder compares the times with your device’s clock and describes the result in words: “expired 2 days ago” for exp, “not valid yet” with the time left for nbf, or a warning that iat is in the future, which points at the issuer’s clock. A value like 1767229200000 is flagged as milliseconds; read as seconds it lands tens of thousands of years in the future, a common bug when code uses Date.now() instead of Math.floor(Date.now() / 1000).
To read the claims in code without verifying, Python’s standard library is enough:
import base64, json
def b64url_decode(part):
return base64.urlsafe_b64decode(part + "=" * (-len(part) % 4))
header_b64, payload_b64, signature_b64 = token.split(".")
print(json.loads(b64url_decode(payload_b64)))
In Node.js, JSON.parse(Buffer.from(payload_b64, "base64url").toString("utf8")) does the same. For authorization decisions, use a library that verifies the signature and pins the accepted algorithms. More snippets are on the Python and Node.js pages.
To build or re-sign a token for testing, use the JWT encoder. A single segment can be decoded on its own with the Base64URL decoder.
FAQ
Can I decode a JWT without the secret key?
Yes. The header and payload are only Base64URL-encoded JSON, so anyone holding the token can read them. The secret or public key is needed only to check that the signature is genuine.
Why does the decoder say the signature is invalid?
Usually the key is wrong or in the wrong form. For HS256, check whether the secret is stored as Base64 or hex and change Secret is to match. For RS256 or ES256, make sure the public key belongs to the token’s kid; with a whole JWKS pasted, the tool picks the key by kid for you.
How do I see when a JWT expires?
Look at the exp claim. It is a count of seconds since 1970-01-01 UTC, and the claims table shows it as local time, UTC and a relative time such as “expired 2 days ago”. A token without exp never expires on its own.
Can this tool decrypt a JWE?
No. For a five-part encrypted token it decodes the protected header and shows alg, enc and kid. Decrypting the payload would need the recipient’s private or shared key.
Does the token get sent anywhere when I paste it?
No. Decoding and verification run in your browser with JavaScript and WebCrypto, and the page’s Content Security Policy blocks every network request except Cloudflare’s page-view count.