How to decode a JWT
A JSON Web Token is three Base64URL strings joined by dots: header.payload.signature. Decoding simply reverses the Base64URL encoding of the first two parts to reveal JSON — no key is needed, which is exactly why a JWT payload must never hold secrets. Paste a token above (with or without the Bearer prefix) and the decoder shows:
- Header — the signing algorithm (
alg), token type (typ) and often a key ID (kid). - Payload — the claims: who the token is about, who issued it, who it is for, when it expires, plus any custom claims such as roles or scopes.
- Claim table — each registered claim explained, with timestamps converted to UTC, IST and your local time and an Expired, Not yet valid or Active badge.
How to verify a JWT (JWT verify)
Verifying proves the token was issued by someone holding the key and has not been modified. It has two parts, and a backend must do both on every request:
- Signature: recompute the signature over
header.payloadwith the expected algorithm and key, and compare. In the panel above, paste the HMAC secret forHS*tokens, or the issuer’s public key forRS*,PS*andES*tokens. Identity providers publish these keys at a JWKS URL, typicallyhttps://<issuer>/.well-known/jwks.json; paste the whole JWKS and the matchingkidis chosen for you. - Claims: reject the token if
expis in the past ornbfin the future (allow a small clock skew, usually 30–60 seconds), and check thatissandaudmatch what your service expects.
Always pin the algorithm on the server side. Accepting whatever alg the token declares enables the classic attacks: alg: none tokens with no signature, and HS256 tokens “signed” with an RSA public key used as an HMAC secret.
Registered JWT claims (RFC 7519)
| Claim | Name | What to check |
|---|---|---|
iss | Issuer | Exactly equals your identity provider’s issuer URL |
sub | Subject | The user or client ID the token represents |
aud | Audience | Contains your API’s identifier (string or array) |
exp | Expiration time | Seconds since epoch; reject when now ≥ exp |
nbf | Not before | Reject when now < nbf |
iat | Issued at | Useful for max-age policies and debugging clock skew |
jti | JWT ID | Unique ID for replay detection or revocation lists |
Timestamps are Unix seconds. To convert one by hand, use the Unix timestamp converter.
JWT signing algorithms compared
| alg | Type | Verify with | Typical use |
|---|---|---|---|
HS256 / 384 / 512 | HMAC + SHA-2 | The same shared secret | Single service that both issues and checks tokens |
RS256 / 384 / 512 | RSA PKCS#1 v1.5 | Public key (PEM / JWK) | Auth0, Okta, Cognito, Azure AD, Google, Keycloak |
PS256 / 384 / 512 | RSA-PSS | Public key | Open Banking, FAPI profiles |
ES256 / 384 / 512 | ECDSA P-256 / P-384 / P-521 | Public key | Apple, mobile and IoT, smaller signatures |
none | Unsigned | — | Never accept in production |
Decode and verify JWTs in code
JavaScript (Node.js, jose)
import { jwtVerify, createRemoteJWKSet, decodeJwt } from "jose";
decodeJwt(token); // decode only, no verification
const JWKS = createRemoteJWKSet(new URL("https://issuer.example.com/.well-known/jwks.json"));
const { payload } = await jwtVerify(token, JWKS, {
issuer: "https://issuer.example.com", audience: "api.example.com", algorithms: ["RS256"],
});
Python (PyJWT)
import jwt
jwt.decode(token, options={"verify_signature": False}) # decode only
jwt.decode(token, "your-256-bit-secret", algorithms=["HS256"]) # verify HS256
jwt.decode(token, public_key_pem, algorithms=["RS256"], audience="api.example.com")
Java (jjwt)
Claims claims = Jwts.parser()
.verifyWith(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)))
.build()
.parseSignedClaims(token)
.getPayload();
Go (golang-jwt)
tok, err := jwt.Parse(s, func(t *jwt.Token) (any, error) {
return []byte("your-256-bit-secret"), nil
}, jwt.WithValidMethods([]string{"HS256"}), jwt.WithAudience("api.example.com"))
Bash (decode only)
cut -d. -f2 <<< "$TOKEN" | tr '_-' '/+' | base64 -d 2>/dev/null | jq .
Common questions
Is it safe to paste a JWT into this decoder?
Decoding and verification run entirely in your browser; the token, secret and key are never sent over the network or stored. Still, treat a live token like a password — prefer expired or test tokens, and revoke any production token you have shared.
Does decoding a JWT verify it?
No. Decoding is just Base64URL, which anyone can do. A token is trustworthy only after its signature is verified with the right key and its exp, nbf, iss and aud are checked.
How do I verify a JWT signature?
For HS256/384/512 paste the shared secret (tick Base64-encoded if it is stored that way). For RS, PS and ES algorithms paste the public key as PEM, a JWK or a whole JWKS; the key matching the token’s kid is used.
Why is exp such a big number?
exp, iat and nbf are seconds since 1 January 1970 UTC — Unix timestamps. The claim table shows each as a date in UTC, IST and your local time, with how long ago or ahead it is.
What is the difference between HS256 and RS256?
HS256 uses one shared secret to sign and verify, so every verifier can also mint tokens. RS256 signs with a private key and verifies with a public key, so APIs can check tokens without being able to issue them.
Can I edit the claims and re-sign the token?
Changing any character of the header or payload breaks the signature. Issuing a new valid token needs the secret or private key, which only the issuer should hold — do it in server code with a JWT library.
Is the JWT payload encrypted?
No. A signed JWT is only Base64URL-encoded, so anyone holding it can read the claims. Encrypted tokens (JWE) have five parts instead of three and cannot be decoded without the key.