About the JWT Decoder
Paste a token into this JWT decoder to read its header and payload, see exp, iat and nbf as dates in your own time zone, and find out right away whether it has expired. The token is decoded by JavaScript in your browser and isn't sent anywhere.
A JSON Web Token (RFC 7519) is three Base64URL strings joined by dots: header.payload.signature. You'll meet them as OAuth 2.0 access tokens, OpenID Connect ID tokens and API session tokens. Each claim gets a one-line explanation, so an unfamiliar azp or at_hash doesn't send you to the spec. Decoding only reads a token, though. It doesn't prove the token is genuine, and the signature isn't checked here.
- Paste the token into JWT. If you copied the whole
Authorizationheader value, theBearerprefix is removed for you. Load sample fills in a test token. - Read the badges first: Expired, Not yet valid, Not expired or No expiry (exp) claim, then the algorithm, such as alg: HS256. Signature not verified is always there, since checking needs a key the page doesn't have.
- On the Payload tab, Timestamps shows each time claim in your local time, as a relative time such as "in 3 hours", and as a UTC ISO date. The status refreshes every 30 seconds.
- Next, Claims lists every claim with a short description for the registered and common OpenID Connect ones. The JSON block below has Copy and Download buttons.
- Switch to the Header tab for
alg,typandkid, each explained, or to Signature for the raw Base64URL signature. - If decoding fails, the message says why, such as the wrong number of parts or a part that isn't Base64URL JSON. A five-part token is an encrypted JWE: you'll see its header and a notice that the payload needs the decryption key.
InputeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE5MTYyMzkwMjJ9.drt_po6bHhDOF_FJoH7IXXeSvnlaxtP7hqw6ylYVHZwOutputHeader: {"alg":"HS256","typ":"JWT"}
Payload: {"sub":"1234567890","name":"John Doe","iat":1516239022,"exp":1916239022}
Issued at (iat): 2018-01-18T01:30:22.000Z
Expires (exp): 2030-09-21T16:37:02.000Z
Status: Not expiredThe status is checked against your device's clock, so a wrong system clock gives a wrong answer.
InputeyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjIwMjYtMDEifQ.eyJpc3MiOiJodHRwczovL2xvZ2luLmV4YW1wbGUuY29tIiwic3ViIjoiMjQ4Mjg5NzYxMDAxIiwiYXVkIjoid2ViLWFwcCIsImF6cCI6IndlYi1hcHAiLCJlbWFpbCI6ImFtYXJhLm9rYWZvckBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiQW1hcmEgT2thZm9yIiwibm9uY2UiOiJuLTBTNl9XekEyTWoiLCJhdXRoX3RpbWUiOjE3NjcyMjU1NDAsImlhdCI6MTc2NzIyNTYwMCwiZXhwIjoxNzY3MjI5MjAwfQ.wA9JIkKb7LzFsc6WTuNZm8jziXldg9nasVok2-aw7TUiseA7E2LLNTCmP4XENC0unS7YNJyz69LUfa6KmPLqqAOutputHeader: {"alg":"ES256","typ":"JWT","kid":"2026-01"}
Payload: {"iss":"https://login.example.com","sub":"248289761001","aud":"web-app","azp":"web-app","email":"amara.okafor@example.com","email_verified":true,"name":"Amara Okafor","nonce":"n-0S6_WzA2Mj","auth_time":1767225540,"iat":1767225600,"exp":1767229200}
Issued at (iat): 2026-01-01T00:00:00.000Z
Expires (exp): 2026-01-01T01:00:00.000Z
Logged in (auth_time): 2025-12-31T23:59:00.000Z
Status: ExpiredAn ID token tells your app who logged in. aud and azp name the client app, nonce ties the token to one login request, and kid says which of the issuer's keys signed it. This one was valid for an hour.
InputeyJhbGciOiJub25lIn0.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.OutputHeader: {"alg":"none"}
Payload: {"sub":"admin","role":"admin"}
Badges: No expiry (exp) claim, alg: none (unsigned)
Signature tab: This token has no signature, so anyone can change its claims.Anyone can type a token like this by hand. Your server must reject it.
What's inside a JWT
A signed JWT (a JWS, RFC 7515) has three parts, each Base64URL-encoded without padding, so the Base64 Decoder can read any one of them. That's encoding, not encryption: never put passwords, API keys or other secrets in a payload.
- Header: JSON naming the signing algorithm (
alg), usually the type (typ: "JWT"), and often a key ID (kid) that tells the server which key to verify with. - Payload: JSON with the claims, such as who the token is about, who it's for and when it expires.
- Signature: computed over
header.payloadwith a shared secret or a private key.
Registered and OpenID Connect claims
RFC 7519 registers seven claims. All are optional, but most tokens carry several. The time claims are seconds since 1970-01-01 UTC, not milliseconds; the Unix Timestamp Converter converts one by hand.
iss(issuer): who created the token, usually the login server's URL.sub(subject): the user or service the token is about.aud(audience): who the token is for. An API must reject tokens meant for another audience.exp(expiration time): don't accept the token at or after this time.nbf(not before): don't accept it before this time.iat(issued at): when the token was created.jti(JWT ID): a unique ID, used to stop the same token being replayed.- ID tokens from OpenID Connect add
azp,nonce,auth_time,at_hash,email,email_verifiedandname. Access tokens often carryscopeorscpandroles.
HS256, RS256 and ES256
The alg header tells you how the token was signed:
- HS256: HMAC with SHA-256 and one shared secret, the same function the Hash Generator computes when you give it a key. The secret both signs and verifies, so any service that can check tokens can also create them.
- RS256: an RSA signature (RSASSA-PKCS1-v1_5) with SHA-256. The login server signs with its private key, and anyone can verify with the public key, often published as a JWKS at
/.well-known/jwks.json. - ES256: ECDSA with the P-256 curve and SHA-256. Same key model as RS256, with much shorter keys and signatures.
Why a token that decodes fine is still rejected
A clean decode only means the token is well formed. When an API still answers 401, compare the claims with what it checks:
exphas passed. Libraries usually allow a minute or so of clock drift, but beyond that you need a new token, for example from the refresh token.nbforiatis in the future. The clocks on the login server and the API disagree. Sync both with NTP, or allow a small leeway.auddoesn't match. A common case is sending the ID token to an API that expects an access token: the ID token's audience is your client app, not the API.issdoesn't match, for example a token from the staging login server sent to production, or an issuer URL with a trailing slash where the API expects none.- The
kidisn't in the issuer's JWKS. The signing keys were rotated and the API still has the old set cached, so it needs to fetch the JWKS again. - The
algisn't on the API's list. That's the API doing its job: it should never let the token choose its own algorithm.
Decoding is not verification
Anyone can create a token with any claims, and this page would show it the same way. Trust a token only after your server has checked the signature with the right key and then checked exp, nbf, iss and aud. Use a maintained library for this.
Allow only the algorithms you expect. Libraries that trusted the token's own alg have been tricked into accepting alg: none, or into checking an RS256 token as HS256 with the public key used as the HMAC secret.
The last snippet only decodes, like this page. Use it for display or debugging, never to decide who gets access.
import { jwtVerify, createRemoteJWKSet } from "jose";
const JWKS = createRemoteJWKSet(new URL("https://login.example.com/.well-known/jwks.json"));
const { payload } = await jwtVerify(token, JWKS, {
algorithms: ["RS256"],
issuer: "https://login.example.com",
audience: "api",
});import jwt # pip install PyJWT
payload = jwt.decode(
token,
secret,
algorithms=["HS256"],
audience="api",
issuer="https://login.example.com",
)keyFunc := func(t *jwt.Token) (any, error) { return []byte(secret), nil }
token, err := jwt.Parse(tokenString, keyFunc,
jwt.WithValidMethods([]string{"HS256"}),
jwt.WithIssuer("https://login.example.com"),
jwt.WithAudience("api"),
)const part = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
const payload = JSON.parse(
new TextDecoder().decode(Uint8Array.from(atob(part), (c) => c.charCodeAt(0)))
);Is it safe to paste a JWT into this decoder?
Decoding happens in this tab, and the token isn't sent to toolo or anyone else. A live token is still a bearer credential: whoever holds it can call your API as that user until exp, so use test or expired tokens in screenshots and bug reports.
Can I verify a JWT signature here?
No. Verification needs the shared secret (HS256) or the issuer's public key (RS256, ES256), and it belongs on your server, in a maintained library. The In code tab shows how with jose, PyJWT and golang-jwt.
Can a JWT be decoded without the secret?
Yes. The header and payload are only Base64URL-encoded, so anyone holding the token can read them. The secret or key is needed only to create a signature or check one.
How do I check if a JWT has expired?
Compare the exp claim with the current time. It's in seconds since 1970-01-01 UTC, not milliseconds, so in JavaScript compare exp * 1000 with Date.now(). The decoder does this for you and shows Expired, Not yet valid or Not expired.
What is the difference between JWT, JWS and JWE?
JWT is the claims format. A JWS is a signed JWT with three parts that anyone can read. A JWE is an encrypted JWT with five parts, and only the holder of the decryption key can read its payload.
What is the difference between an ID token and an access token?
An ID token is for your app: it says who logged in, and its aud is your client ID. An access token is for an API and says what the caller may do. Don't send ID tokens to APIs, and don't rely on the contents of an access token unless your provider documents its format.
Why does my JWT not decode?
A signed JWT has exactly three parts separated by two dots, so check that you copied all of it. Quotes copied along with the token, or a token cut off at the end of a log line, also cause errors.