Why Decoding JWT Payloads Is Not Verification
Debug JWT issues by separating Base64URL decoding from signature verification, claim validation, algorithm checks, expiration and audience matching.
Quick Answer
Decoding a JWT only reveals the header and payload. It does not prove the token is valid, trusted, unexpired or intended for your API. Verification must check the signature, algorithm, issuer, audience, expiration, not-before time and any application-specific claims.
Example Scenario
A developer pastes a token into a decoder, sees the expected user id and assumes the token is valid. The API still rejects it. The signature may be invalid, the token may be expired, the audience may target another service, or the decoded payload may have been edited because decoding is not a trust check.
Step-by-Step Explanation
- Decode header and payload only for inspection.
- Verify the signature with the expected key and algorithm.
- Validate issuer, audience, expiration and not-before claims.
- Check whether NumericDate claims use seconds.
- Reject tokens with unexpected alg values or missing required claims.
- Avoid logging full tokens during debugging.
JWT Parts Have Different Jobs
A JWT commonly has three dot-separated Base64URL parts: header, payload and signature. The header describes metadata such as algorithm and key id. The payload contains claims. The signature protects the header and payload from tampering when verified with the correct key.
Base64URL decoding the first two parts is not a secret operation. Anyone holding the token can do it. That is useful for debugging, but it does not make the claims trustworthy.
The signature is what connects the readable claims to a trusted issuer. Without verification, the payload is just text that may have been modified.
Algorithm and Key Selection Matter
Verification must use the expected algorithm and key. A token signed with a different key or algorithm should fail even if the payload looks reasonable. If the header includes a key id, the verifier needs to choose the matching trusted key from the issuer configuration.
Never let untrusted token headers decide everything without policy. Your application should know which issuer and algorithms it accepts. Accepting unexpected algorithms can create serious security bugs.
During debugging, log safe metadata such as alg, kid, issuer and audience. Do not log the full token or signing keys.
Claims Are Part of Verification
A valid signature is necessary but not always sufficient. The token also needs to be intended for your API. Check iss, aud, exp, nbf and any tenant or scope claims your application requires.
Audience mismatches are common when one identity provider issues tokens for multiple APIs. A token may be valid for the user profile API but not for your billing API. The decoded user id can look correct while the token is still not meant for this service.
Expiration and not-before claims are usually NumericDate values in seconds. Comparing them with Date.now() without conversion can create false expired or false valid results.
Decoders Are Still Useful
A decoder helps inspect claim shape, timestamps and audience quickly. It is useful when diagnosing why verification failed, as long as the team remembers that decoded data is untrusted until verification passes.
Use decoding to answer questions such as which issuer produced the token, what audience it names, when it expires and whether expected scopes are present. Then use a verifier to decide whether the token is acceptable.
For sensitive systems, prefer local or internal tools for token inspection and redact tokens from tickets. Tokens can grant access even when they are only used for debugging.
Failure Evidence
Separate signature failure from claim failure in logs. Both should reject the request, but they point to different fixes. Signature failure suggests wrong key, wrong issuer, modified token or algorithm mismatch. Claim failure suggests audience, expiration, tenant, scope or clock issues.
Include request id, issuer, audience, alg, kid and failure reason when safe. Do not include token body, signature or secrets. This gives enough context to debug without creating a credential leak.
When clock issues are suspected, log server UTC time and token exp or nbf as formatted UTC values after converting seconds correctly.
Verification Checklist
Check token structure, expected issuer, allowed algorithm, trusted key, signature, audience, expiration, not-before, scopes and tenant context. Any one of these can make a readable token unacceptable.
Add tests for expired token, wrong audience, wrong issuer, modified payload and unknown key id. Negative tests are important because a verifier that accepts too much is worse than one that rejects a valid token loudly.
If the app accepts multiple issuers or audiences, keep that mapping explicit. Ambiguous token acceptance rules are difficult to audit.
For debugging dashboards, display verification outcome separately from decoded claim preview. This prevents support teams from seeing a readable user id and assuming the request should have been accepted.
When keys rotate, test old valid token, new valid token and token signed by an unknown key. Key rotation bugs often look like random authentication failures unless kid, issuer and verification reason are logged together.
Clock tolerance deserves explicit policy. A small leeway can absorb minor server clock drift, but a large leeway weakens expiration checks. Log how much skew was accepted when token time claims are near the boundary.
Scope and permission checks should happen after token verification, not instead of it. A decoded scope string from an unverified token is not authorization evidence. First prove who issued the token, then decide what the verified claims allow.
If tokens come from multiple identity providers, normalize claim names inside a trusted adapter. Do not let application code guess whether user id lives in sub, uid or another provider-specific field on every request.
Check token type as well as token content. An id token may describe authentication for a client application, while an access token is usually what an API should receive. Passing an id token to an API can look plausible when decoded, but the audience and intended use are wrong.
For support cases, ask for the verification failure reason rather than the token itself. A reason such as expired, unknown kid or audience mismatch is actionable and much safer to share than a bearer token.
When a token is rejected, compare the API clock, issuer metadata fetch time and key cache age. A verifier can fail a good token if it has stale signing keys or if the server clock is far enough from the issuer clock.
For browser applications, remember that storing a token and decoding it for display are separate from proving the API will accept it. The final authority is the API verifier, not the client-side decoded preview.
Code Examples
function decodeJwtPart(part) {
const base64 = part.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(atob(base64));
}
const [header, payload] = token.split('.').map(decodeJwtPart); const nowSeconds = Math.floor(Date.now() / 1000);
if (payload.exp <= nowSeconds) {
throw new Error('Token is expired');
} console.log({
alg: header.alg,
kid: header.kid,
iss: payload.iss,
aud: payload.aud,
reason: 'audience_mismatch'
}); Common Mistakes
- Treating decoded JWT payload as trusted.
- Checking user id but skipping audience and issuer.
- Comparing exp seconds with Date.now milliseconds.
- Accepting whatever algorithm appears in the token header.
- Pasting full access tokens into shared debugging tools.
FAQ
Is JWT payload encrypted?
Usually no. Standard JWT payloads are Base64URL encoded and readable unless a separate encryption format is used.
Does decoding prove the token is valid?
No. Decoding only reveals text. Verification checks signature and claims.
Why does the API reject a readable token?
It may have invalid signature, wrong audience, wrong issuer, expired claims or missing scopes.
Are exp values seconds or milliseconds?
JWT NumericDate values such as exp are commonly seconds since the Unix epoch.