JWT And API Token Security
Core Controls
- Use short expiry times and validate issuer, audience, signature algorithm, and time claims.
- Never place secrets or unnecessary personal data in a JWT payload.
- Store opaque API tokens hashed when the server only needs to verify them.
- Plan revocation for logout, compromised credentials, and role changes.
- Send bearer tokens only over HTTPS and avoid logging them.
Opaque Tokens Are Often Simpler
If the same application issues and verifies a token, an opaque random token can be easier to revoke and reason about.
<?php
declare(strict_types=1);
$token = bin2hex(random_bytes(32));
$storedDigest = hash('sha256', $token);
echo strlen($token) . PHP_EOL;
echo strlen($storedDigest) . PHP_EOL;
// Prints:
// 64
// 64
Store the digest rather than the raw token when the server only needs to compare a presented credential. Show the raw token once to the client, just as you would with a password-reset token.
JWT Verification Is More Than Decoding
A JWT payload is base64url-encoded, not encrypted. Anyone holding the token can often read its claims.
Use a maintained library and configure it narrowly. Verify the signature with the expected algorithm and key. Check expiry, not-before time where used, issuer, audience, and any application-specific claims. Do not trust the token because it parses successfully.
Revocation And Rotation
Decide what happens when a token leaks, a user logs out, a staff member leaves, or permissions change. Short-lived access tokens reduce exposure. Refresh tokens, API keys, and signing keys need rotation and revocation plans.
Keep tokens out of URLs because URLs appear in histories, logs, analytics, and referrer headers. Redact authorization headers in logs and monitoring tools.
In Application Work
JWT libraries should be configured narrowly. For each API, decide whether JWTs provide a real benefit over simpler opaque credentials.
What To Check
Before moving on, make sure you can explain bearer-token risk, compare opaque tokens with JWTs, describe complete JWT verification, and plan expiry, revocation, rotation, transport, and logging controls.
A JWT Is A Signed Claim Set, Not A Session Database
A JSON Web Token commonly contains a protected header, a claim set, and a signature. Base64url decoding reveals the header and claims to anyone who has the token; ordinary signed JWTs are not encrypted. Never place passwords, private keys, or unnecessary personal data in them.
The signature can prove that an accepted key signed the bytes. It does not prove that the current request is authorized, that the account still exists, or that the token has not been revoked. Those decisions belong to application policy after cryptographic verification.
Pin Algorithms And Key Sources
Use a maintained JWT library and configure the algorithms the application accepts. Do not select verification behavior from an untrusted alg header without an allowlist. Symmetric and asymmetric algorithms use different key assumptions; accepting the wrong family can create key-confusion vulnerabilities.
Choose keys from a trusted issuer configuration or key set. When using JWKS, match a bounded key identifier, validate the issuer's endpoint through trusted configuration, and cache keys with a controlled refresh policy. An unknown key ID may justify one refresh, not unbounded network requests for attacker-chosen identifiers.
Key rotation requires overlap. Publish the new verification key before issuing tokens with it, retain the old public key until all valid old tokens expire, and remove compromised keys according to an incident procedure. Monitor unknown-key and signature failures so rotation mistakes are visible.
Validate Claims For This API
Verification must check the expected issuer and audience, not merely the signature. An access token valid for another service should not be accepted here. Define whether the audience claim may be a string or array according to the issuer contract and library behavior.
Check expiration and not-before times with a small documented clock-skew allowance. Large leeway extends token lifetime and can hide clock problems. Synchronize server clocks and test exact boundary seconds. An issued-at claim can support diagnostics and maximum-age policy, but it is not a substitute for expiration.
Require claims the application actually depends on, validate their types, and reject duplicates or malformed structures according to the library. Map the subject and tenant through trusted application data before accessing a resource.
Authentication Is Followed By Authorization
A valid token identifies an authenticated principal under the issuer's claims. The API must still decide whether that principal may perform the requested action on the specific resource. A scope such as orders:read may permit the kind of operation without granting access to every customer's order.
Keep coarse scopes and roles separate from ownership, tenant, state, and business-policy checks. Test a valid token that requests another user's object, a token from another tenant, and a privileged action with an ordinary scope. Successful signature verification is only the first gate.
Token Lifetime, Revocation, And Sessions
Short access-token lifetimes limit exposure but increase refresh traffic. Refresh tokens are more powerful credentials and need secure storage, rotation, reuse detection, and revocation. Browser applications should prefer architectures that keep long-lived credentials out of JavaScript-accessible storage where practical.
JWTs are self-contained, so immediate revocation is not free. Options include short lifetimes, a token-version check, a denylist for high-risk events, or an authorization-server introspection endpoint. Each adds state or latency. Choose based on incident and logout requirements rather than assuming statelessness is always the priority.
Opaque tokens can be simpler when one authorization server and a few APIs need central control. The API introspects the token or looks up a server-side session, gaining immediate revocation and smaller credentials at the cost of a dependency. JWT is a format choice, not a maturity level.
Bearer Token Transport
Send bearer access tokens in the Authorization header over HTTPS. Tokens in query strings can leak through history, logs, analytics, and referrer headers. Reject tokens from unexpected locations instead of accepting several ambiguous transport mechanisms.
Configure trusted proxies correctly so the application knows whether the original connection was secure. Redact authorization headers from application and reverse-proxy logs. Error responses should distinguish missing or invalid authentication from insufficient permission without exposing verification internals.
Failure Handling And Observability
Return a generic authentication failure publicly for bad signatures, expired tokens, wrong issuer, malformed claims, and unknown keys. Internally, record a low-cardinality reason, issuer, key identifier where safe, and request correlation ID. Never log the complete token.
A sudden increase in unknown key IDs may indicate an issuer rotation problem or hostile traffic. Expiration spikes may indicate clock drift or a client refresh defect. Authorization denials need separate metrics from cryptographic failures because they have different owners and remedies.
Do not retry token verification after a deterministic claim failure. A temporary JWKS fetch problem may be retryable within strict limits if cached valid keys cannot verify the token, but authentication paths must not amplify an outage with a request storm.
Testing The Complete Boundary
Use tokens generated for tests with dedicated keys. Cover altered signatures, disallowed algorithms, missing claims, wrong issuer, wrong audience, future not-before, exact expiration boundaries, malformed claim types, unknown key IDs, and key rotation overlap.
Then test authorization with cryptographically valid tokens: another user's resource, another tenant, missing scope, stale role, disabled account, and an operation forbidden by resource state. These cases prove that token acceptance does not bypass application policy.
Run integration tests through the real HTTP middleware and proxy configuration. Verify that authorization headers are redacted, HTTPS requirements work, and errors use the intended HTTP status. Exercise issuer outage and stale-key behavior without allowing uncontrolled refresh attempts.
Prevent Token-Type Confusion
An identity token, access token, refresh token, email-verification token, and password-reset token may all be represented as JWTs, but they are not interchangeable. Give each token type a distinct audience, purpose claim or validation configuration, key where appropriate, and consuming endpoint. An API must not accept an OpenID Connect ID token merely because its signature comes from the same issuer.
Keep validation entry points separate so a caller cannot choose a permissive profile. The password-reset handler should require its short lifetime and one-time state; the API middleware should require the API audience and access-token claims. Test that every valid token type is rejected by every wrong endpoint.
Encrypted JWTs add confidentiality but also key management and nested-token rules. Use them only through a supported protocol profile. Encryption does not replace signature or claim validation, and a custom encrypted token format is rarely safer than storing opaque state server-side.
Local Development Tokens
Development shortcuts should not weaken production validation code. If tests need tokens, generate them with a dedicated local key and the same issuer, audience, algorithm, and time checks used by the application. Avoid an environment flag that accepts unsigned tokens or skips claim validation, because one misplaced configuration value can turn a test helper into a production bypass. Integration tests should prove the production middleware rejects those local-only credentials when the configured issuer and key differ.
After this lesson, you should be able to verify JWT signatures with pinned algorithms and trusted keys, validate issuer, audience, and time claims, design rotation and revocation, separate authentication from resource authorization, protect bearer-token transport, and test both cryptographic and policy failures.
Practice
Practice: Review An API Token Flow
Requirements
- Define token lifetime and revocation behaviour.
- Validate issuer, audience, algorithm, and expiry.
- Keep bearer tokens out of URLs and logs.
- Explain whether an opaque token would be simpler.
Show solution
Review Points
- Use short expiry times and validate issuer, audience, signature algorithm, and time claims.
- Never place secrets or unnecessary personal data in a JWT payload.
- Store opaque API tokens hashed when the server only needs to verify them.
- Plan revocation for logout, compromised credentials, and role changes.
- Send bearer tokens only over HTTPS and avoid logging them.