How to decode a JWT token

To decode a JWT, split the token at the two dots and Base64url decode the first two parts: the first is the header (algorithm and token type) and the second is the payload with the claims, both plain JSON. Decoding needs no key, because a JWT is signed, not encrypted; the key is only needed to verify the third part, the signature. The decoder on this page does both and never sends the token anywhere.

6 min read

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 page

The 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.

ClaimNameMeaning
issIssuerWho created the token, often the URL of the identity provider
subSubjectWho the token is about, usually a user ID
audAudienceWhich API or app should accept it
expExpiration timeAfter this moment the token must be rejected
nbfNot beforeBefore this moment the token is not valid yet
iatIssued atWhen the token was created
jtiJWT IDUnique ID, used to block reuse or revoke a token
auth_timeAuthentication timeWhen 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.

Step by step

  1. Paste the tokenPaste the JWT into the input. A Bearer prefix is fine.
  2. Read header and payloadThe header and payload appear as formatted JSON, with the token's dates in your local time.
  3. Check the statusThe status line shows whether the token is expired, not valid yet, unsigned or encrypted.
  4. Verify the signatureEnter the shared secret for HS algorithms or the PEM public key for RS, PS and ES to see whether the signature is valid.

Frequently asked questions

Can I decode a JWT without the secret key?

Yes. The header and payload are only Base64url encoded, so anyone can read them. The secret or public key is needed only to verify the signature.

Is it safe to paste my token into this decoder?

Yes. Decoding and verification run in your browser, and the token is never sent to a server or stored. It is free and works offline once the page has loaded.

How do I know when a JWT expires?

Look at the exp claim, a Unix timestamp in seconds. The tool converts it to your local date and time and tells you how long ago it expired or how long it has left.

Which signature algorithms can be verified?

HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 and ES512.

Is a JWT encrypted?

A normal JWT (JWS) is signed, not encrypted, so its content is readable. Encrypted tokens (JWE) have five parts and need the private key to read.

Why does my decoded payload show strange characters?

The payload was probably decoded as Latin 1 instead of UTF-8, or the token was copied incompletely. This tool decodes UTF-8 correctly, so names with accents or other alphabets show as intended.