How to decode a JWT and check whether it is valid
The short answer. Decoding a JWT only reads it: split it at the two dots and Base64URL-decode the first two parts to see the header and the claims. Checking it is valid means verifying the signature with the issuer's secret or public key, then checking that exp has not passed, nbf has, and iss and aud are the values your service expects.
A JSON Web Token is a small signed note. A login server writes some claims about a user, signs them, and hands the result to the browser or app, which sends it back with each request. Anyone holding the token can read it; only someone with the key can make a new one that verifies. That difference, readable but not forgeable, is the whole point, and it is why "decode" and "validate" are separate steps.
What is inside a JWT
A token looks like this, three parts joined by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTQyIiwiaWF0IjoxNzkxMjEyNDAwLCJleHAiOjE3OTEyMTYwMDB9.LNV6F4X2KYqv02h8_uZOlWecsEu9NHOIlhjnkMd-VgY
| Part | Decodes to | What it says |
|---|---|---|
| Header | {"alg":"HS256","typ":"JWT"} |
signed with HMAC-SHA256 |
| Payload | {"sub":"user-42","iat":1791212400,"exp":1791216000} |
the claims: who, issued when, expires when |
| Signature | 32 bytes of binary | proof that the first two parts were signed with the key |
The first two parts are Base64URL, which is Base64 with - and _ in place of + and / and the = padding removed. They are encoded, not encrypted: anyone can read them. Never put a password or anything secret in a JWT's payload.
The claims that decide validity
| Claim | Name | Check |
|---|---|---|
exp |
expiration time | the current time must be before it |
nbf |
not before | the current time must be at or after it |
iat |
issued at | informational; some services reject tokens issued too long ago |
iss |
issuer | must equal the issuer you trust, such as your auth server's URL |
aud |
audience | must include your service's identifier |
sub |
subject | the user or client the token is about |
exp, nbf and iat are NumericDate values: seconds since 1 January 1970 UTC, not milliseconds. In the example, exp 1791216000 is 16:00 UTC on 5 October 2026, one hour after iat. Servers usually allow a minute or two of clock skew on these checks.
How to decode and check a JWT
- Copy the whole token, all three parts. In a browser's developer tools it is usually in the
Authorization: Bearer ...request header, or in local storage or a cookie. - Paste it into the JWT Decoder. The header and payload appear as JSON, each claim with a plain-English line, and the expiry as a date with a valid or expired badge.
- Read
algin the header. That tells you what you need to verify it: a shared secret forHS256,HS384orHS512; the issuer's public key forRS256orES256. - Verify the signature: paste the secret, or the public key as PEM or JWK. A token that fails here must be rejected, whatever its claims say.
- Check the claims against what your service expects: not expired,
nbfpassed,issandaudcorrect.
A worked example
Take the token above and suppose it is 15:30 UTC on 5 October 2026.
- Header:
HS256, so verification needs the shared secret. - Signature: with the secret
example-secret, it verifies. With any other secret, it fails. - exp: 16:00 UTC, which is 30 minutes away, so the token is still in date.
- iss and aud: absent. A service that requires them must reject this token even though the signature is good.
At 16:00:01 the same token is expired, and a correct server rejects it even though nothing about the token has changed.
Why decoding is not validating
Because the payload is only encoded, anyone can write a token that decodes to {"sub":"admin"}. What they cannot do without the key is make the signature match. Code that reads claims from a token without verifying the signature trusts whatever the user sends. Three well-known mistakes follow from that:
- Trusting
alg: none. An unsigned token says"alg":"none"and has an empty signature. Libraries must refuse it unless explicitly configured otherwise. - Letting the token choose the algorithm. If a server verifies with whatever
algthe header names, an attacker can switch an RS256 token to HS256 and sign it with the public key as an HMAC secret. Fix the expected algorithm on the server. - Skipping
aud. A token issued for one service can be replayed to another that trusts the same issuer. Check the audience.
These and other pitfalls are set out in RFC 8725, the IETF's best current practice for JWTs.
If it doesn't work
- "Invalid token" or nothing decodes. The token was cut short or has extra characters. Copy it again; it must have exactly two dots and no spaces or quotes. Drop the
Bearerprefix. - The signature fails with the right secret. Check for a trailing space or newline in the secret, and whether the issuer expects it Base64-decoded first. For RS256 or ES256, you need the public key, not the private key or a certificate fingerprint.
- It shows expired but was just issued. The clock on the server or your machine is wrong, or a value in milliseconds was written into
exp. A 13-digitexpis a bug in the issuer. - The payload is unreadable binary. That is a JWE, an encrypted token with five parts rather than three. It can only be read with the recipient's decryption key.
- The API still rejects a valid-looking token. Check
audandiss, the scopes or roles claim, and whether the token was revoked; a JWT cannot show revocation by itself.
Questions people ask
How do I check if a JWT is valid?
Verify its signature with the issuer's secret or public key, then check the claims: exp must be in the future, nbf in the past, and iss and aud must match what your service expects. Only a token that passes all of these is valid. Decoding it, which anyone can do, shows its contents but proves nothing about whether to trust it.
Can a JWT be decoded without the secret key?
Yes. The header and payload are Base64URL-encoded JSON, not encrypted, so anyone holding the token can read them without any key. The key is needed only to verify the signature or to create a new token. For that reason a JWT payload should never contain a password, a card number or anything else that must stay secret.
What does exp mean in a JWT?
exp is the expiration time: the moment after which the token must not be accepted. Its value is a NumericDate, the number of seconds since 1 January 1970 UTC, so 1791216000 means 16:00 UTC on 5 October 2026. Servers usually allow a minute or two of leeway for clocks that disagree slightly.
Is it safe to paste a JWT into an online decoder?
Only into one that runs in your browser. A live access token works like a temporary password until it expires, so a site that uploads it could use it. The JWT decoder here decodes and verifies with your browser's WebCrypto, and neither the token nor the secret is sent anywhere or stored. For production keys, verify on your own server.
What is the difference between HS256 and RS256?
HS256 signs and verifies with one shared secret, so every service that checks the token can also create tokens. RS256 signs with a private key that only the issuer holds and verifies with a public key that anyone may have. RS256 or ES256 suit tokens checked by many services; HS256 suits a single service that issues and checks its own.
Sources
Checked 5 Oct 2026:
- RFC 7519, JSON Web Token (JWT) (section 4.1: the registered claims
iss,sub,aud,exp,nbf,iat,jti; section 2: NumericDate in seconds) - RFC 7515, JSON Web Signature (JWS) (the three-part compact form and Base64URL encoding)
- RFC 7518, JSON Web Algorithms (JWA) (HS256, RS256, ES256 and
none) - RFC 8725, JSON Web Token Best Current Practices (algorithm confusion,
none, audience checks)