***

title: JWT Verification
subtitle: Verify access tokens locally and refresh JWKS on key rotation
slug: integration-patterns/jwt-verification
-------------------------------------------

Verify Gwop access tokens locally with `jose`. Do not call the API on every request just to check a signature.

## Why this pattern

* JWT signature verification stays local and fast
* Gwop JWKS is fetched on cold start
* the verifier refreshes JWKS only when a token carries an unknown `kid`
* `sid` is preserved so the app can perform a live session lookup when revocation matters

## Recommended shape

```typescript
import {
  createLocalJWKSet,
  decodeProtectedHeader,
  jwtVerify,
} from "jose";

let cachedJwks: {
  keys: Array<Record<string, unknown> & { kid?: string }>;
  getKey: ReturnType<typeof createLocalJWKSet>;
} | null = null;

async function refreshJwks() {
  const { result } = await gwop.auth.getJwks(identityRequestOptions);
  const keys = result.keys as Array<Record<string, unknown> & { kid?: string }>;

  cachedJwks = {
    keys,
    getKey: createLocalJWKSet({ keys }),
  };

  return cachedJwks;
}

async function getJwksForToken(token: string) {
  const header = decodeProtectedHeader(token);

  if (!cachedJwks) {
    return refreshJwks();
  }

  if (
    typeof header.kid === "string" &&
    !cachedJwks.keys.some((key) => key.kid === header.kid)
  ) {
    return refreshJwks();
  }

  return cachedJwks;
}

async function verifyAccessToken(token: string) {
  const jwks = await getJwksForToken(token);
  const { payload } = await jwtVerify(token, jwks.getKey, {
    issuer: "https://identity.gwop.io",
    algorithms: ["RS256"],
  });

  return {
    subject: payload.sub,
    sessionId: payload.sid,
  };
}
```

## What this does not replace

Local JWT verification proves signature, issuer, audience, and expiry. It does not replace a live session lookup if you need to reject revoked sessions immediately. Keep `sid` so you can pair local verification with `gwop.authSessions.get()` when needed.

## Related pages

<CardGroup cols={2}>
  <Card title="JWKS" icon="duotone lock" href="/auth/jwks">
    See the SDK endpoint that provides the signing keys for local verification
  </Card>

  <Card title="Session vs Token" icon="duotone id-card" href="/concepts/session-vs-token">
    Learn when local verification is enough and when to check the live session
  </Card>

  <Card title="Sessions" icon="duotone badge-check" href="/auth/sessions">
    Use the session API when you need revocation-aware auth decisions
  </Card>
</CardGroup>
