JWT Deep Dive: What's Inside a Token and How to Validate It Safely
JSON Web Tokens are a common access-token format, but OAuth 2.0 does not require them: providers may issue opaque bearer tokens that an API introspects instead. When a token is a JWT, treating its three dot-separated segments as a trusted black box is how validation mistakes become vulnerabilities.
This article opens that format. You'll see what is inside a signed JWT, how symmetric and asymmetric algorithms change key ownership, which checks apply to your token profile, how refresh tokens work, and the mistakes that have enabled token forgery.
Apply these checks within the broader ASP.NET Core authorization model. For API contract and error-handling guidance, continue with REST API design best practices.
Anatomy of a JWT
A JWT is three Base64URL-encoded strings joined by dots: header.payload.signature. They're not encrypted (unless you use JWE, a separate spec) — they're signed. Anyone can decode the header and payload. The signature is what you trust.
The header declares the token type and signing algorithm. The payload holds claims — standardized fields like iss (issuer), sub (subject/user ID), exp (expiration), aud (intended audience), and iat (issued-at), plus any custom claims your application adds. The signature is computed over the header and payload using the algorithm declared in the header and a secret or private key held by the authorization server.
Quick reference
- Base64URL encoding ≠ encryption. Anyone with the token can decode the header and payload.
- Never put secrets (passwords, credit cards, SSNs) in JWT claims — treat them as public data.
- exp is a Unix timestamp in seconds (not milliseconds). Off-by-1000x is a common bug.
- iat records issuance time; invalidating pre-reset tokens requires server-side state or a policy that compares iat with a stored cutoff.
- Custom claims should use namespaced keys (https://myapp.com/role) to avoid collision with registered claims.
Remember this
A JWT is a signed assertion, not an encrypted secret. Its claims are readable by anyone — only trust the signature.
HS256 vs RS256: Which Algorithm to Use
HS256 (HMAC-SHA256) uses a shared secret to sign and verify tokens. Every verifier that can validate the token can also mint one, so key distribution and verifier trust become important as the system grows.
RS256 (RSA-SHA256) separates a private signing key from public verification keys, commonly published through JWKS. That separation is often a better fit for many independently operated verifiers, but algorithm choice also depends on ecosystem support, key management, compliance profile, token size, and measured signing/verification workload.
Quick reference
- HS256: one secret, shared everywhere. Simple but risky in distributed systems.
- RS256: private key signs, public keys verify; rotation still requires overlapping keys and correct kid/JWKS caching.
- ES256: smaller keys and signatures, but performance and interoperability depend on libraries and hardware — benchmark your workload.
- PS256: RSA-PSS is required or recommended by some security profiles; it is not a universal upgrade for every deployment.
- Fetch public keys from the JWKS endpoint at startup and cache them — don't fetch per-request.
- Rotate signing keys with a key ID (kid) header — publish new key, let old tokens expire, then remove old key.
Remember this
Prefer asymmetric signing when verifiers should not be able to mint tokens; choose the supported algorithm from your threat model and profile.
Validating a JWT: Every Step Matters
JWT validation is not just decoding or signature verification. For a typical signed access token, validate the signature, trusted algorithm, issuer, audience, and time constraints required by the token profile. A correctly signed but expired token must still be rejected; a token for another audience is not intended for your API.
Use a maintained library and configure it for the issuer's documented profile. Revocation or replay checks are separate policy decisions that require server-side state; they are not mandatory cryptographic checks for every JWT.
Quick reference
- 1. Verify signature — reject tokens where header + payload + key doesn't match the signature.
- 2. Check algorithm (alg) — reject 'none'. Only accept your expected algorithm (RS256, ES256).
- 3. Validate issuer (iss) — only accept tokens from your auth server.
- 4. Validate audience (aud) — only accept tokens issued for your API.
- 5. Check expiry (exp) — reject tokens past their expiration, with a small clock-skew tolerance.
- 6. Check not-before (nbf) — reject tokens not yet valid.
- 7. Apply revocation/replay policy when the threat model requires it — for example introspection, a jti blocklist, or a token-version cutoff.
Remember this
Validate signature, an explicit algorithm allowlist, issuer, audience, and applicable times; add revocation state when required.
Access Tokens and Refresh Tokens
Shorter-lived access tokens reduce the time a stolen token remains useful, but the right lifetime follows threat model, request pattern, and the availability of revocation or sender-constraining. Refresh tokens are longer-lived credentials used to obtain new access tokens without repeating the full login flow.
Store refresh tokens using the platform's protected credential mechanism. Browser applications often use an HttpOnly, Secure cookie with a SameSite mode chosen for the actual cross-site OAuth flow. Rotation can detect reuse when the server stores token-family state and invalidates the old token.
Quick reference
- Access-token lifetime: choose from risk, latency, workload, and revocation design; minutes are common for interactive high-risk APIs, not a universal rule.
- Refresh-token lifetime: choose from risk, session policy, rotation, and user experience rather than a fixed range.
- Refresh token rotation: issue a new refresh token on every refresh, invalidate the old one. Detects theft.
- For browsers, prefer HttpOnly and Secure cookies; choose SameSite=Strict, Lax, or None based on the login/callback flow and add CSRF defenses.
- Sliding expiry: extend refresh token lifetime on activity. Absolute expiry: force re-login after max time.
- Token revocation: maintain a blocklist (Redis) of revoked jti (JWT ID) values for high-risk operations like logout.
Remember this
Use bounded access-token lifetimes and protected refresh-token rotation when the client profile needs sessions; justify both from the threat model.
JWT Mistakes That Cause Real Vulnerabilities
The most famous JWT vulnerability is the algorithm confusion attack: an attacker changes the header from RS256 to HS256, then signs the token with the server's public key as the HMAC secret. Libraries that blindly trusted the alg claim would then verify the forged token using the public key — and succeed. Modern libraries have fixed this, but it illustrates why you must specify the expected algorithm in your validator, never accept the algorithm from the token itself.
Other common mistakes include no expiry (tokens valid forever), storing tokens in localStorage (vulnerable to XSS), skipping audience validation (any token from your auth server is accepted by any service), and hardcoding weak secrets (HS256 with 'secret' or 'changeme' — brute-forceable in seconds).
Quick reference
- Algorithm confusion (alg:none or RS256→HS256): always specify accepted algorithms in your validator.
- No expiry (exp): every token should expire. For long-running machine processes, rotate tokens on schedule.
- localStorage storage: XSS can steal tokens. Use httpOnly cookies or memory storage for SPAs.
- Weak HS256 secrets: use at least 256 bits of cryptographically random bytes. Not a password.
- Missing audience validation: your auth server issues tokens for multiple services — validate aud.
- Logging tokens: never log Authorization headers. Treat tokens like passwords in logs.
Remember this
Whitelist the algorithm in your validator (never trust the token's alg claim), set expiry on every token, and store tokens in httpOnly cookies when possible.
Key takeaway
JWT safety comes from a documented token profile and a correctly configured library: restrict algorithms, validate signature, issuer, audience, and applicable times, then add replay or revocation state where the risk requires it. If your provider issues opaque access tokens, follow its introspection contract instead of trying to decode them.
Practice: write negative tests for a bad signature, wrong algorithm, issuer, audience, expired token, and not-before time. Add one test proving your password-reset or logout policy rejects an older token only if you actually maintain the required server-side cutoff or revocation state.
Related Articles
Explore this topic