Utilido

Utilido blog

JWT claims to check before you trust a token

Header alg, exp, aud, and iss fields worth reading during API integration, plus a decode-first checklist that does not replace signature verification.

  • jwt
  • security
  • api
  • oauth
  • debugging

By · Published May 4, 2026 · Updated May 27, 2026 · 7 min read

JSON Web Tokens show up in OAuth callbacks, service-to-service auth, and mobile session handoffs. During integration you often paste a bearer string into a decoder to see why a route returns 401. That step is useful, but it is not the same as trusting the token. The developer tools hub on Utilido groups local helpers for this kind of inspection, including a JWT decoder that parses header and payload without sending the token to a remote service for the decode step.

This article is a practical read order for claims before you blame the wrong layer: clock skew, audience mismatch, or an algorithm surprise in the header. Verification with your issuer keys still belongs on the server.

The three segments and what each one holds

A JWT is three Base64url-encoded pieces separated by dots: header, payload, and signature. The header usually states the signing algorithm (alg) and sometimes a key id (kid). The payload holds claims: registered names like exp and aud, plus custom fields your product adds. The signature covers the first two segments so a verifier can detect tampering.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
 |______________ header ______________|.|___________ payload ___________|.|____ signature ____|

Decoding reveals JSON; it does not prove that JSON was signed by your issuer. Treat decoded claims as hints during debugging, then confirm with JWKS, a shared secret, or whatever your identity provider documents.

Decode first, verify on the server

Paste the token into the JWT decoder to read alg, exp, and aud in one view. If the payload is gzip-compressed or nested, you still only see what was encoded, not whether the signature matches.

Decode is not verify A valid-looking payload can be forged. Authorization decisions belong in your API after signature verification, issuer checks, and audience checks against your configured values.

Your API should reject tokens with unexpected alg values, missing exp, or aud strings that do not include your service identifier. Client-side decode helps humans; server-side verify protects data.

Header: start with alg and kid

Read the header before the payload. Integration bugs often trace to alg: none, a switch from RS256 to HS256, or a kid that does not match any key in your JWKS cache.

Header fieldWhat to check
algMatches what your docs promise; no none in production
typUsually JWT; odd values may indicate the wrong token type
kidPresent when the issuer rotates keys; must resolve in JWKS

If your library auto-accepts several algorithms, pin the allowed set in configuration. Algorithm confusion attacks exploit loose acceptance lists.

exp and clock skew

The exp claim is a NumericDate: seconds since the Unix epoch in UTC, not milliseconds. A value that decodes to 1970 or the year 2286 often means the issuer sent milliseconds, or you are reading the wrong field. The dedicated guide Unix timestamps: telling seconds from milliseconds walks through digit counts and sanity checks with a converter.

{
  "sub": "user_42",
  "exp": 1716397200,
  "iat": 1716393600,
  "nbf": 1716393600
}

Allow a small leeway for clock skew between issuers and your servers (commonly 30 to 120 seconds). Reject tokens that expired long ago, and treat nbf in the future the same way you treat a late exp.

When logs show “token expired” but the decoder says exp is tomorrow, compare server clocks with NTP and confirm both sides use UTC internally.

aud, iss, and azp: who the token is for

iss (issuer) should match the authority you configured: your Auth0 tenant, Cognito pool URL, or corporate IdP identifier. aud (audience) lists intended recipients. A token issued for another API will fail audience checks even when the signature is valid.

Some providers add azp (authorized party) for clients in OAuth flows. Document which claim your middleware enforces so staging and production do not drift. Multi-audience tokens arrive as strings or arrays depending on the library; normalize before comparison.

sub, scopes, and custom claims

sub identifies the subject, often a stable user id. Do not treat sub alone as authorization: pair it with scopes, roles, or permissions claims your policy engine understands.

Custom claims (permissions, tenant_id, email) are product-specific. Name them consistently across services and avoid putting secrets in the payload. JWT payloads are only encoded, not encrypted, unless you use nested JWE, which is a different integration path.

iat, nbf, and session freshness

iat records when the token was issued. Short-lived access tokens may omit interesting iat drama, but long-lived refresh tokens sometimes expose stale sessions when iat is months old.

nbf (“not before”) blocks early use. If servers drift, a token can look valid on exp yet fail on nbf. Log both timestamps when debugging intermittent 401 responses.

A practical inspection checklist

Use this order when a partner sends a sample token:

  • Confirm three dot-separated segments; copy without line breaks from chat tools.
  • Decode header: note alg and kid.
  • Decode payload: write down iss, aud, sub, exp, and scope or role claims.
  • Compare exp in UTC; if the year is wrong, re-check seconds vs milliseconds.
  • Match aud and iss to your environment config (staging vs production).
  • Only then run signature verification with the correct key material.

For related encoding questions (padding, URL-safe Base64), see Base64 in config files and logs.

Verification belongs on the server

Fetch JWKS from the issuer, cache keys with rotation in mind, and validate signature, exp, iss, and aud in one middleware path. Shared-secret HS256 is fine for internal services when the secret is stored in a vault and never checked into git.

Log claim failures with reason codes (aud_mismatch, expired, bad_signature) instead of a generic “unauthorized” when safe for your threat model. Operators fix misconfigured clients faster when the reason is visible in staging logs.

Common integration mistakes

  • Trusting decode output in the browser and skipping server verify.
  • Comparing aud to the wrong string (client id vs API identifier).
  • Storing refresh tokens in localStorage without understanding XSS impact (a storage topic, but it shows up next to JWT debugging).
  • Assuming email in the payload was verified by the issuer when it was only self-asserted.

Pair JWT work with structured JSON debugging when APIs return opaque errors: JSON debug checklist after API changes.

The JWT mistake I still see in staging

I still catch teams pasting a production-issued token into a staging API and wondering why aud fails. The signature verifies, the clock is fine, and an hour disappears because nobody wrote the expected audience next to the environment name on the wiki. I now decode one known-good token per environment at the start of a sprint and pin the iss and aud strings in the ticket. It is boring, and it has outlasted every “quick” fix that disabled audience checks “just for testing.”

FAQ

Does decoding a JWT prove it is authentic?

No. Decoding only reveals the header and payload bytes. Anyone can craft an unsigned payload. Your API must verify the signature with the issuer keys or shared secret.

Why does my exp claim show 1970 or a far-future year?

exp is usually Unix seconds. A 13-digit value may be milliseconds misread as seconds. Use the Unix timestamp guide and confirm UTC when comparing to server logs.

Should I accept alg none in development?

Do not accept none in any environment that touches real data. Use the same algorithm policy in development and production so surprises appear early.

What is the difference between aud and azp?

aud names the intended recipients of the token. azp often names the OAuth client that requested the token. Your framework may require one or both; follow issuer documentation instead of guessing.

Can I use the JWT decoder on production tokens?

The Utilido decoder runs locally in the browser for the decode step. Treat production tokens like passwords in screen shares: prefer synthetic tokens in tickets, and rotate if a live bearer token leaked into a chat log.

About the author

, Software engineer. Benchehida Abdelatif builds Utilido: fast browser utilities for images, PDFs, and developer workflows, with client-side processing where it matters for privacy. More about Utilido.