Decoding in one step
A JSON Web Token looks like eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMifQ.signature: three Base64url strings joined by dots. Every JWT starts with eyJ because that is how the characters {" look in Base64.
Paste the token into the tool below. A Bearer prefix copied from an Authorization header is accepted. You see the header and payload as formatted JSON, the exp, iat, nbf and auth_time claims as dates in your local time with a relative hint, and a status line that says whether the token has expired or is not valid yet. To check the signature, enter the shared secret or the public key in Secret or public key (optional).
Try it here: JWT Decoder
Open the full tool pageThe three parts of a JWT
- Header: a small JSON object such as {"alg":"HS256","typ":"JWT"}. alg names the signing algorithm; kid, if present, identifies which key was used, which helps when the issuer rotates keys.
- Payload: the claims, meaning statements about the user and the token, for example the user ID, the issuer, the audience, roles and expiry time. Anyone holding the token can read them.
- Signature: calculated over the encoded header and payload with a secret (HMAC) or a private key (RSA or elliptic curve). It proves the first two parts were not changed and were issued by someone who holds the key.
- A token with five parts instead of three is an encrypted JWE. Its payload cannot be read without the private key, and the tool tells you when you paste one.
Common claims and what they mean
The JWT standard (RFC 7519) registers a few short claim names. Times are NumericDate values: seconds since 1 January 1970 UTC, not milliseconds. Mixing these up is a classic bug, for example comparing exp with JavaScript's Date.now(), which returns milliseconds.
| Claim | Name | Meaning |
|---|---|---|
| iss | Issuer | Who created the token, often the URL of the identity provider |
| sub | Subject | Who the token is about, usually a user ID |
| aud | Audience | Which API or app should accept it |
| 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 the token was created |
| jti | JWT ID | Unique ID, used to block reuse or revoke a token |
| auth_time | Authentication time | When the user actually signed in (OpenID Connect) |
Decoding by hand or in code
Base64url differs from standard Base64: it uses minus and underscore instead of + and /, and the = padding is removed. Most decode failures come from feeding a JWT part straight into a standard Base64 decoder. Replace minus with + and underscore with /, add = until the length is a multiple of 4, and then decode.
In Python, base64.urlsafe_b64decode(part + "==") handles the alphabet, and the extra padding is tolerated. In the browser console, atob works after you swap the two characters, but it returns bytes as Latin 1, so non English names in the payload need a TextDecoder for correct UTF-8. Libraries such as PyJWT, jsonwebtoken for Node.js or System.IdentityModel.Tokens.Jwt for .NET decode and verify in one call; use their verifying function in production, not a decode only shortcut.
For a quick look, a tool is faster and avoids leaving the token in your shell history.
Decoding is not verifying
Reading the payload tells you what the token claims; it does not prove that the claims are true. Anyone can create a token with "role": "admin" and Base64 encode it. Only a valid signature, checked with the right key, makes the content trustworthy. Servers must always verify before using any claim.
In the tool, HS256, HS384 and HS512 tokens are verified with the shared secret. RS, PS and ES tokens are verified with the issuer's public key in PEM format, the block that begins with BEGIN PUBLIC KEY. If your identity provider publishes keys only as a JWKS list, convert the matching key (the one whose kid matches the header) to PEM first. The result reads Signature is valid or Signature does not match the key. If you paste a private key, the tool asks for the public one instead.
Watch for these red flags: alg set to none (the token has no signature at all and the tool marks it as unsigned), an unexpected algorithm (a server expecting RS256 must never accept an HS256 token, a known attack), and tokens whose iss or aud do not match your system.
Troubleshooting expired and invalid tokens
- Token expired: check the exp date in the Dates section. Access tokens often live only 5 to 60 minutes by design; the app should use a refresh token to get a new one.
- Not valid yet: nbf or iat is in the future, which usually means the server clock and the issuer clock differ. Many libraries allow a small clock skew of a few seconds to a few minutes; fix the clock with time synchronization.
- This does not look like a JWT: the token was cut off, wrapped in quotes or contains spaces from copying. Copy it again from the original header or cookie.
- The header or payload could not be read: part of the token is missing. Long tokens are often truncated in logs.
- Signature does not match: wrong secret, the public key of a different environment (staging versus production), a rotated key, or a token that was modified.
Handle tokens safely
A valid access token works like a password until it expires. Do not paste live production tokens into websites that send them to a server, and do not post them in tickets or chat. This decoder runs entirely in your browser: the token is not sent, logged or stored.
On the issuing side, keep the payload small and free of secrets. Because anyone can read it, never put passwords, API keys or personal data that the client should not see into a JWT. Store the user ID and roles, and look up everything else on the server.