For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Apply for AccessDashboard
Guides
Guides
  • Get Started
    • Introduction
    • Quickstart
  • Agent Identity
    • Overview
    • Create Auth Intent
    • Exchange for JWT
    • Sessions
    • JWKS
  • Agent Checkout
    • Overview
    • Create an Invoice
    • List Invoices
    • Get Invoice
    • Cancel Invoice
  • Integration Patterns
    • Overview
    • Shared SDK Client
    • Wallet Auth
    • JWT Verification
    • Subscription Checkout
    • Webhook Verification
  • Concepts
    • Two Invoice IDs
    • Wallet Identity
    • Session vs Token
    • Webhook-Driven State
  • Webhooks
    • Overview
    • Verify Signatures
  • Reliability
    • Errors
    • SDK Reference
Apply for AccessDashboard
On this page
  • Why this pattern
  • Recommended shape
  • What this does not replace
  • Related pages
Integration Patterns

JWT Verification

Verify access tokens locally and refresh JWKS on key rotation
||View as Markdown|
Was this page helpful?
Edit this page
Previous

Wallet Auth

Next

Subscription Checkout

Built with

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

1import {
2 createLocalJWKSet,
3 decodeProtectedHeader,
4 jwtVerify,
5} from "jose";
6
7let cachedJwks: {
8 keys: Array<Record<string, unknown> & { kid?: string }>;
9 getKey: ReturnType<typeof createLocalJWKSet>;
10} | null = null;
11
12async function refreshJwks() {
13 const { result } = await gwop.auth.getJwks(identityRequestOptions);
14 const keys = result.keys as Array<Record<string, unknown> & { kid?: string }>;
15
16 cachedJwks = {
17 keys,
18 getKey: createLocalJWKSet({ keys }),
19 };
20
21 return cachedJwks;
22}
23
24async function getJwksForToken(token: string) {
25 const header = decodeProtectedHeader(token);
26
27 if (!cachedJwks) {
28 return refreshJwks();
29 }
30
31 if (
32 typeof header.kid === "string" &&
33 !cachedJwks.keys.some((key) => key.kid === header.kid)
34 ) {
35 return refreshJwks();
36 }
37
38 return cachedJwks;
39}
40
41async function verifyAccessToken(token: string) {
42 const jwks = await getJwksForToken(token);
43 const { payload } = await jwtVerify(token, jwks.getKey, {
44 issuer: "https://identity.gwop.io",
45 algorithms: ["RS256"],
46 });
47
48 return {
49 subject: payload.sub,
50 sessionId: payload.sid,
51 };
52}

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

JWKS

See the SDK endpoint that provides the signing keys for local verification

Session vs Token

Learn when local verification is enough and when to check the live session

Sessions

Use the session API when you need revocation-aware auth decisions