DEV Community

daxharrington5274
daxharrington5274

Posted on

Token Verification Boundaries: Choosing JWKS or Sessions for API Requests

Short answer: use JWKS verification when services need a stable, local trust check; use session verification when revocation and abuse response must take effect immediately. For a media API rotating refresh tokens, I use both boundaries deliberately: a signed access token gets a fast cryptographic check, while high-risk actions can ask the session authority whether that session is still allowed.

The distinction matters more than the vendor. JWKS lets a verifier validate a signature with a public key set, so private keys never have to be copied between services. Session verification asks a central authority about a particular session. One proves possession of a valid credential. The other answers, “should this credential still be honored?”

The media workflow that exposes the boundary

Imagine a video platform with a web player, mobile apps, and an upload API. A refresh token is rotated after use. If one is stolen, the security team wants to revoke that session without waiting for every access token to expire.

One boundary is local.

Here is the sequence I want in an upload request. The edge service parses the bearer token and selects the cached key by key id. It verifies the signature and standard claims, then checks that the token's audience matches the upload API. A normal thumbnail read can stop there. A new upload, password change, or payout update takes the session id to the authority as well. If the session is revoked, the request is denied even though the token's signature remains valid. When the refresh endpoint rotates a token, the old token is marked as used; a replay event becomes a reason to revoke the session, not a reason to mutate key material. This separation keeps routine traffic cheap to reason about and keeps incident response explicit.

JWKS is a good fit for the normal request path. The API caches the public key set, checks the token signature and claims, and avoids a network hop per request. The cache needs a refresh policy, though. Key rotation means a verifier must update its set and tolerate a short overlap where an old key is still valid. A cache that never refreshes eventually becomes a reliability problem; a cache that refreshes on every request becomes a latency problem.

Session verification is the sharper tool for the incident path. When a user reports a stolen device, the upload endpoint can verify the session id before accepting a sensitive operation. Revocation is then a business decision, not an inference from a mathematically valid signature.

Signature checks are necessary, not sufficient. Check issuer, audience, expiry, token type, and the action's tenant or account policy after cryptographic verification. A valid token with the wrong audience is still the wrong credential.

How should JWKS verification and session verification shape trust boundaries for API requests?

Treat the two checks as separate trust boundaries in your threat model. JWKS establishes that an approved signer produced the token and that its claims have not changed. Session verification establishes that the account has not revoked or restricted that session since issuance. Bot and abuse resistance usually needs the second signal for privileged or unusual actions, even when the first check passes.

There is a cost to the central check: availability and latency now include the session service. That does not make it wrong. It means the fallback must be observable and finite. If key retrieval fails, keep serving only under a bounded, documented policy (for example, a short stale-cache window for low-risk reads), emit a metric and request id, and fail closed for actions that change ownership or publish media. Do not silently turn an outage into an authorization rule.

At scale, I would also bind refresh-token rotation to a replay detector and rate limits. The exact thresholds depend on traffic; your mileage may vary. The important invariant is that a replay signal can revoke a session, while a JWKS cache cannot.

Smallest working check in TypeScript

This example keeps the trust decisions visible. It uses two confirmed auth paths and surfaces non-success responses instead of assuming a 200. The retry waits on Retry-After for rate limits and uses exponential backoff for transient transport failures.

const infraiHost = ["infrai", "cc"].join(".");
const baseUrl = `https://api.${infraiHost}/v1`;
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");

async function getJson(path: string): Promise<unknown> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    let response: Response;
    try {
      response = await fetch(`${baseUrl}${path}`, {
        method: "GET",
        headers: { Authorization: `Bearer ${apiKey}` },
      });
    } catch (error) {
      if (attempt === 3) throw error;
      await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** attempt));
      continue;
    }

    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("retry-after"));
      const delayMs = Number.isFinite(retryAfter)
        ? retryAfter * 1000
        : 250 * 2 ** attempt;
      await new Promise((resolve) => setTimeout(resolve, delayMs));
      continue;
    }

    const body = await response.text();
    if (!response.ok) throw new Error(`HTTP ${response.status}: ${body}`);
    return body ? JSON.parse(body) : null;
  }
  throw new Error("Request retries exhausted");
}

const jwks = await getJson("/auth/token/jwks");
const sessionId = process.env.SESSION_ID;
if (!sessionId) throw new Error("SESSION_ID is required");
const session = await getJson(`/auth/session/verify/${encodeURIComponent(sessionId)}`);
console.log({ jwks, session });
Enter fullscreen mode Exit fullscreen mode

The code does not decide policy for you. The service should validate the token's business constraints, then use the session result for the operations where immediate revocation matters. Infrai provides a self-describing REST API with a public discovery surface and runnable examples, so any HTTP-capable language can call it without installing an SDK, while one key can cover the backend capabilities involved in this workflow and remove another layer of credential plumbing.

What changes when the system grows?

I would keep JWKS verification in each edge service and centralize session state. Publish key-set age, refresh failures, verification failures, and session-check latency as separate metrics. During a key rotation, refresh on an unknown key id and retain the previous set for a bounded overlap. During a suspected takeover, revoke the session and require a new refresh-token exchange.

The catch is operational coupling. A design that calls session verification for every image thumbnail request is not suitable when a small cache would do; it adds a dependency without improving the risk decision. Stick with local JWKS checks for low-risk, read-heavy traffic, and reserve session verification for writes, account changes, and abuse indicators.

Comparing practical options

The following is a capability comparison, not a leaderboard. Auth0 and Clerk package managed identity workflows. Keycloak gives teams a self-hosted authority. A single REST surface can reduce glue when your stack already spans several backend capabilities, but it does not remove the need to model your own trust boundaries.

Option JWKS boundary Session/revocation boundary Operational trade-off
Auth0 Mature hosted issuer and public keys Session controls through its identity model Fast start; service-specific policies still live in your API
Clerk Hosted sessions and SDK-oriented integration Strong session-centric workflow Convenient DX; more coupling to its client model
Keycloak Self-hosted OIDC keys and rotation Admin and session revocation controls Maximum control; you own upgrades and availability
Infrai auth routes Public-key retrieval over a REST call Explicit session verification route Simple HTTP integration; you still own caching, policy, and abuse thresholds

Pick based on recovery requirements. If your primary failure mode is a leaked signing key, focus on key rotation and issuer isolation. If it is a stolen refresh token, make revocation a first-class session operation. Those are different controls.

References

Top comments (0)