Free tool

JWT decoder

Paste a JSON Web Token to read its header and payload, see when it expires in your own time zone, and verify the signature with a secret, a PEM key, a certificate or a JWKS. Everything runs in your browser.

Free · no sign-up · runs in your browser, nothing is uploaded

Encoded token
Header

The header (algorithm, type, key ID) appears here.

Payload

The claims (sub, exp, iat...) appear here, with times in your time zone.

Decoding and verifying run in your browser with the Web Crypto API. Tokens and keys are never sent anywhere or put in the URL.

What is inside a JWT

A signed JWT is three Base64URL strings joined by dots: header.payload.signature. The decoder colors them the same way. The header and payload are JSON; the signature is raw bytes computed over the first two parts, exactly as they appear in the token.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9      header    {"alg":"HS256","typ":"JWT"}
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6...  payload   {"sub":"1234567890","name":"John Doe",...}
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQ   signature HMAC-SHA256(header + "." + payload, secret)

The header says how the token was signed:

ParameterMeaning
algSigning algorithm, such as HS256, RS256 or ES256. "none" means unsigned.
typToken type, usually JWT. RFC 8725 suggests more specific types like at+jwt for access tokens.
kidKey ID. Picks the right public key from the issuer's JWKS.
jku, x5uURLs of a key set or certificate. Never trust them blindly: an attacker can point them at their own key.
x5cThe certificate chain itself, as Base64 DER.

Base64URL is Base64 with - and _ instead of + and /, and no = padding. To decode one part by hand, use the Base64 decoder, which accepts this alphabet as is.

Registered claims and what they mean

RFC 7519 defines seven registered claims. All are optional; which ones a server requires is up to the application. The time claims are NumericDate values: whole or fractional seconds since 1970-01-01 UTC, not milliseconds. A value with 13 digits is almost always a bug, and the decoder warns about it.

ClaimNameWhat a server does with it
issIssuerChecks it is the issuer it expects, and that the signing key belongs to that issuer.
subSubjectUses it as the user or client ID.
audAudienceRejects the token if its own ID is not listed. A string or an array of strings.
expExpiration timeRejects the token on or after this time.
nbfNot beforeRejects the token before this time.
iatIssued atCan reject tokens older than a maximum age.
jtiJWT IDCan remember used IDs to block replays.

The claims view above also explains common OpenID Connect claims (nonce, azp, auth_time, email_verified) and OAuth ones (scope, client_id). To convert any timestamp by hand, use the Unix timestamp converter.

Decoding is not verifying

Anyone can decode a JWT, and anyone can make a new one with whatever claims they like. Only the signature tells you the token came from the issuer and was not changed. Paste the key in the verify box to check it. Which key you need depends on the algorithm in the header:

algAlgorithmWhat to paste to verify
HS256 / HS384 / HS512HMAC with SHA-2The same shared secret the issuer used
RS256 / RS384 / RS512RSA PKCS#1 v1.5Public key PEM, certificate, JWK or JWKS
PS256 / PS384 / PS512RSA-PSSPublic key PEM, certificate, JWK or JWKS
ES256 / ES384 / ES512ECDSA on P-256, P-384, P-521Public key PEM, certificate, JWK or JWKS
EdDSAEd25519 (RFC 8037)Public key PEM or OKP JWK (needs a recent browser)

Most identity providers (Auth0, Okta, Google, Microsoft Entra ID, Keycloak, Clerk) publish their public keys at the jwks_uri listed in {issuer}/.well-known/openid-configuration. If the token has an https issuer, the fetch JWKS from iss button loads that key set for you. Only the issuer's public endpoints are contacted, and some issuers block browser requests; then copy the JWKS from the URL yourself.

The decoder also accepts PEM in every common shape: BEGIN PUBLIC KEY, BEGIN RSA PUBLIC KEY (PKCS#1) and BEGIN CERTIFICATE. Need a token to test with? Make one with the JWT generator.

Decode and verify a JWT in code

JavaScript with jose, which runs in Node, Deno, Bun, browsers and edge runtimes:

import { decodeJwt, decodeProtectedHeader, jwtVerify, createRemoteJWKSet } from 'jose';

decodeJwt(token);               // payload, NOT verified
decodeProtectedHeader(token);   // header

// HS256
await jwtVerify(token, new TextEncoder().encode(secret), { algorithms: ['HS256'] });

// RS256 / ES256 from an identity provider's JWKS
const JWKS = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'));
const { payload } = await jwtVerify(token, JWKS, { issuer: 'https://auth.example.com', audience: 'my-api' });

Node.js with jsonwebtoken:

const jwt = require('jsonwebtoken');
jwt.decode(token, { complete: true });                 // { header, payload, signature }, NOT verified
jwt.verify(token, secret, { algorithms: ['HS256'] });  // throws TokenExpiredError, JsonWebTokenError...

Python with PyJWT:

import jwt
jwt.decode(token, options={"verify_signature": False})        # NOT verified
jwt.decode(token, secret, algorithms=["HS256"], audience="my-api", leeway=30)

Go with golang-jwt v5:

claims := jwt.MapClaims{}
_, _, err := jwt.NewParser().ParseUnverified(tokenString, claims) // NOT verified

token, err := jwt.Parse(tokenString, func(t *jwt.Token) (any, error) {
    return []byte(secret), nil
}, jwt.WithValidMethods([]string{"HS256"}))

Bash, to peek at the payload (adds the padding base64 wants):

p=$(printf %s "$TOKEN" | cut -d. -f2 | tr '_-' '/+')
while [ $(( ${#p} % 4 )) -ne 0 ]; do p="$p="; done
printf %s "$p" | base64 -d | jq .

Always pass the list of algorithms you accept when verifying. RFC 8725 asks for this so a token cannot switch itself to none or to HS256 signed with your public key.

Common JWT errors and what they mean

ErrorLibraryCause and fix
jwt expiredjsonwebtokenNow is on or after exp. Get a new token, or allow a little clock skew with clockTolerance.
ExpiredSignatureErrorPyJWTSame as above; use leeway= for clock skew.
jwt not activejsonwebtokenNow is before nbf. Usually the issuer's and server's clocks differ.
invalid signaturejsonwebtokenWrong secret or key, or the token was edited. Check the secret encoding and the kid.
jwt malformedjsonwebtokenNot three dot-separated parts. Often the "Bearer " prefix or quotes were left in.
jwt audience invalidjsonwebtokenThe aud claim does not include the audience you passed to verify.

FAQ

Can a JWT be decoded without the secret?+

Yes. The header and payload of a signed JWT are only Base64URL-encoded JSON, so anyone who has the token can read them. The secret or key is needed only to verify the signature, which proves the token was not changed. If the payload must stay private, the issuer has to use an encrypted JWE instead.

Is it safe to paste my JWT into an online decoder?+

Only if the site decodes it in your browser. This one does: decoding and verification run in JavaScript with the Web Crypto API, and the token is not sent to DevHunt or put in the URL. Still, a live access token works for anyone who holds it until it expires, so prefer expired or test tokens when you can.

How do I check if a JWT is expired?+

Read the exp claim: it is a NumericDate, the number of seconds since 1970-01-01 UTC. The token is expired when the current time is on or after exp. This decoder shows exp as a date in your time zone and as "expires in 12 minutes" or "expired 3 hours ago". In JavaScript: Date.now() / 1000 >= payload.exp.

What is the difference between decoding and verifying a JWT?+

Decoding turns the Base64URL parts back into JSON so you can read the claims. Verifying recomputes the signature with the secret or public key and checks it matches, which proves who issued the token and that nobody edited it. Servers must verify before trusting any claim; decoding alone proves nothing.

Why does my JWT say "invalid signature"?+

Usually the key is wrong: a different secret, the secret entered as text when it is really Base64, or a public key that does not match the kid in the header. It also fails if any character of the header or payload changed, including whitespace added when the token was copied. For ES256, the signature must be the 64-byte R and S pair, not a DER-encoded signature.

What does the kid in the JWT header mean?+

kid (key ID) tells the receiver which key signed the token. Issuers that rotate keys publish several public keys in a JWKS (JSON Web Key Set), each with its own kid. Paste the whole JWKS into the verify box and this decoder picks the key whose kid matches.

Can you decode a JWE (encrypted JWT)?+

Only the header. A JWE has five parts instead of three, and its payload is encrypted, so it cannot be read without the decryption key. This tool recognises a five-part token and tells you it is a JWE rather than showing an error.

What do iat, nbf and exp mean in a JWT?+

iat (issued at) is when the token was created, nbf (not before) is the time before which it must be rejected, and exp (expiration time) is the time on or after which it must be rejected. All three are seconds since 1970-01-01 UTC. RFC 7519 lets servers allow a few minutes of leeway for clock skew.

Sources: RFC 7519 (JWT) · RFC 7515 (JWS) · RFC 7518 (JWA algorithms) · RFC 8037 (EdDSA in JOSE) · RFC 8725 (JWT best practices) · RFC 7516 (JWE) · OpenID Connect Discovery · jsonwebtoken (Node.js) · PyJWT · jose (JavaScript) · golang-jwt · MDN: SubtleCrypto.verify(). Checked 2026-10-30.