Blog ·
Decode JWT vs Verify: What's the Difference?
Decoding a JWT only reads its header and payload. Verifying checks the signature and claims with a trusted key. Here is how each works, with code.
JWT Decoder
Decode a JWT's header and payload instantly.
Decoding a JWT reads the header and payload, which are only base64url-encoded JSON. It needs no key and proves nothing about who issued the token. Verifying a JWT checks the signature with a key you trust and then validates claims such as exp, nbf, iss and aud. Only verification tells you the token is authentic and currently valid.
To look inside a token, paste it into the JWT Decoder. It shows the header, the payload, and the iat, nbf and exp claims as readable dates. It does not check the signature. That makes it the right tool for debugging and the wrong tool for deciding whether to trust a request. The tool runs entirely in your browser, but a live token is still a credential, so treat it that way.
What a JWT contains
A JSON Web Token has three parts separated by dots, in the form header.payload.signature.
- Header: JSON describing the algorithm (alg) and token type (typ). For example: {"alg":"HS256","typ":"JWT"}.
- Payload: JSON claims such as sub, iat, exp, and any application data.
- Signature: a value computed over the first two parts, using a shared secret (HMAC, such as HS256) or a private key (RSA or ECDSA, such as RS256 or ES256).
The header and payload are base64url-encoded, not encrypted. Anyone holding the token can read them. The signature is what makes the token tamper-evident: changing the payload invalidates it.
Decoding: reading without trusting
Decoding splits the token on dots, base64url-decodes the first two segments, and parses them as JSON. No secret is involved, so anyone can do it. A decoded payload can be edited and re-encoded, and a decoder will display the edited version without complaint.
Base64url differs from standard base64. It uses - and _ instead of + and /, and it usually omits = padding. Plain atob() in JavaScript or base64.b64decode() in Python often fails on it unless you convert the characters and restore the padding first.
const [headerB64, payloadB64] = token.split('.');
const header = JSON.parse(Buffer.from(headerB64, 'base64url').toString('utf8'));
const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf8'));
console.log(payload.exp);import base64, json
def b64url_json(segment):
padded = segment + '=' * (-len(segment) % 4)
return json.loads(base64.urlsafe_b64decode(padded))
header_b64, payload_b64, _sig = token.split('.')
header = b64url_json(header_b64)
payload = b64url_json(payload_b64)Use decoding for debugging, for reading non-sensitive claims in logs, or for reading exp before you call a verifier. Never make an authorization decision from decoded claims without verifying first.
Verifying: checking the signature and the claims
Verification has two parts. First, the signature must match the header and payload under a key you already trust. Second, the claims must be valid for your application at the current time.
- Pin the algorithm. Accept only the algorithms you expect, such as HS256 or RS256. Do not take alg from the token and trust it.
- Verify the signature with the correct key. For HS256 this is your shared secret. For RS256 or ES256 it is the issuer's public key.
- Check exp and nbf. Reject tokens that have expired or are not yet valid. Both jsonwebtoken and PyJWT enforce these by default. Allow a small clock skew if your servers disagree on the time.
- Check iss and aud. Confirm the token came from the expected issuer and is meant for your service. In jsonwebtoken, iss and aud are checked only when you pass the expected values. In PyJWT, if the token contains an aud claim and you pass no audience, the call raises an error instead of skipping the check. Either way, you must pass the values your service expects.
iat is not checked against the current time in a way that rejects ordinary tokens. In jsonwebtoken, iat is enforced only if you set a maximum token age with the maxAge option, which rejects tokens issued too long ago. PyJWT validates iat as well, and recent versions reject an iat that is in the future. So the exact behavior depends on the library and version, and you should check the documentation for the one you use.
Skipping the algorithm check has caused real vulnerabilities. Some libraries have accepted alg set to none, which means no signature at all, and some have allowed an RSA public key to be used as an HMAC secret. Always pass an explicit allowlist of algorithms.
Verifying with a library
Use a maintained library rather than writing your own signature check. Here are two common calls with the algorithm pinned.
const jwt = require('jsonwebtoken');
const claims = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'],
issuer: 'https://auth.example.com',
audience: 'my-api',
});import jwt
claims = jwt.decode(
token,
key=PUBLIC_KEY_PEM,
algorithms=['RS256'],
audience='my-api',
issuer='https://auth.example.com',
)The Python function is a naming trap. In PyJWT, jwt.decode verifies the signature and the claims. It is not a read-only decode. To skip verification you must opt out explicitly with options={'verify_signature': False}, which you should only do for inspection. Node's jsonwebtoken splits the two: jwt.decode reads without checking, and jwt.verify checks.
Both calls throw or raise if the signature is wrong, the token is expired, or the issuer or audience does not match. If you get claims back, the checks you configured passed.
Quick comparison
- Needs a key: reading no, verifying yes.
- Detects tampering: reading no, verifying yes.
- Checks exp and nbf: reading shows the values, verifying enforces them.
- Checks iat: reading shows the value; verifying enforces it only when configured, such as jsonwebtoken's maxAge.
- Good for: debugging, inspecting claims, reading the expiry date.
- Good for: authenticating requests and trusting claims in your API.
Common mistakes
- Treating a decoded token as valid. If the signature was never checked, the token could be forged.
- Verifying the signature but not the expiry, or disabling exp checks to fix a clock problem. A correctly signed token from last year passes the signature check unless you enforce exp.
- Using the wrong key type. Passing a public key where a secret is expected, or the reverse, either fails or, in unsafe setups, gets exploited.
- Forgetting audience checks. A token issued for one service may be valid for another if you do not check aud.
- Sharing live tokens. A bearer token grants access to whoever holds it. Treat any production token as a credential, and do not paste one into a tool you have not vetted.
To check the exp claim specifically, see JWT Expiration Time: How to Check the exp Claim. The exp value is seconds since the Unix epoch, so you can convert it with the Unix Timestamp Converter if you need a date.
When to use which
Decode when you are inspecting a token during development, reading claims for logs, or checking why a request was rejected. Verify whenever a token is used to make a decision: granting access, trusting a user ID, or accepting data from another system. If your code reads a claim before verifying, move the verification earlier.
FAQ
Can I verify a JWT without the secret?
Not for symmetric algorithms like HS256, because the secret is the key. For asymmetric algorithms like RS256, you need the issuer's public key, often published at a JWKS endpoint. You never need the private key to verify.
Does a JWT decoder tell me if the token is valid?
No. A decoder shows the contents and can show whether exp has passed. It does not check the signature, so a token with a forged payload will still decode cleanly.
Is a JWT encrypted?
Usually not. A standard signed JWT is readable by anyone who has it. Encrypted tokens (JWE) exist, but they are a different format and need a decryption key. Do not put secrets in a plain JWT payload.
Why does my decoder show the token but my API still rejects it?
The API is probably verifying the signature, checking exp, or checking iss and aud against its own configuration. Compare the decoded claims with what your server expects, and confirm the algorithm in the header is on your allowlist.
JWT Decoder
Decode a JWT's header and payload instantly.