---
title: Cloudflare Workers | Keycard
description: Keycard auth for Cloudflare Workers — JWT verification, token exchange, and isolate-safe caching.
---

The Cloudflare Workers package adapts Keycard’s OAuth primitives to Workers’ `fetch(request, env)` handler model. It’s the Workers-native equivalent of [`@keycardai/mcp`](/sdk/mcp/index.md) (Express).

## When to Use

- Deploying an MCP server or API on Cloudflare Workers
- Building Code Mode workers that need Keycard auth
- Any Worker that needs JWT verification + token exchange with isolate-safe caching

Note

This package is TypeScript only. For Express-based servers, use [`@keycardai/mcp`](/sdk/mcp/index.md).

## Installation

Terminal window

```
npm install @keycardai/cloudflare
```

## Key Exports

`@keycardai/cloudflare`

| Export                  | Description                                                         |
| ----------------------- | ------------------------------------------------------------------- |
| `createKeycardWorker`   | High-level wrapper — chains metadata, auth, and your handler        |
| `verifyBearerToken`     | Verify JWTs from `Request` — returns `AuthInfo` or error `Response` |
| `handleMetadataRequest` | Serves `.well-known` OAuth endpoints, returns `Response` or `null`  |
| `IsolateSafeTokenCache` | Per-user token cache — safe for CF isolate reuse                    |
| `resolveCredential`     | Auto-detect credential type from env bindings                       |
| `WorkersClientSecret`   | Application credential using client ID + secret                     |
| `WorkersWebIdentity`    | Application credential using private key JWT (no secret needed)     |

## Quick Start

```
import { createKeycardWorker } from "@keycardai/cloudflare";


export default createKeycardWorker({
  resourceName: "My MCP Server",
  scopesSupported: ["mcp:tools"],
  requiredScopes: ["mcp:tools"],


  async fetch(request, env, ctx, auth) {
    // auth is verified — auth.subject, auth.scopes, auth.token available
    return new Response(JSON.stringify({
      message: "Hello from Keycard!",
      user: auth.subject,
    }), {
      headers: { "Content-Type": "application/json" },
    });
  },
});
```

`createKeycardWorker` automatically handles:

- **CORS preflight** responses
- **OAuth metadata** at `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`
- **Bearer token verification** with JWKS, scope checks, and expiration validation
- **JWKS serving** at `/.well-known/jwks.json` when using WebIdentity

## Delegated Access (Token Exchange)

Exchange the user’s Keycard token for an upstream API token using `IsolateSafeTokenCache`:

```
import {
  createKeycardWorker,
  IsolateSafeTokenCache,
  resolveCredential,
} from "@keycardai/cloudflare";
import { TokenExchangeClient } from "@keycardai/oauth/tokenExchange";


let tokenCache: IsolateSafeTokenCache;


function getCache(env) {
  if (!tokenCache) {
    const credential = resolveCredential(env);
    const client = new TokenExchangeClient(
      env.KEYCARD_ISSUER,
      credential.getAuth() ?? undefined,
    );
    tokenCache = new IsolateSafeTokenCache(client, { credential });
  }
  return tokenCache;
}


export default createKeycardWorker({
  async fetch(request, env, ctx, auth) {
    const cache = getCache(env);
    const token = await cache.getToken(
      auth.subject!,
      auth.token,
      "https://api.github.com",
    );


    const response = await fetch("https://api.github.com/user", {
      headers: { Authorization: `Bearer ${token.accessToken}` },
    });
    return new Response(await response.text());
  },
});
```

Caution

**Never use module-level token caches** in Workers. Cloudflare reuses isolates across requests — a module-level cache would leak user A’s token to user B. `IsolateSafeTokenCache` keys by `${subject}::${resource}` to prevent this.

## Credential Modes

### Client Credentials (client ID + secret)

The simplest setup. Create application credentials in Keycard Console and store them as Worker secrets:

Terminal window

```
wrangler secret put KEYCARD_CLIENT_ID
wrangler secret put KEYCARD_CLIENT_SECRET
```

### Web Identity (private key JWT)

No client secret needed. The Worker generates JWT client assertions signed with a private key:

Terminal window

```
# Generate and store a private key
openssl genrsa 2048 | wrangler secret put KEYCARD_PRIVATE_KEY
```

The Worker automatically:

1. Serves its public key at `/.well-known/jwks.json`
2. Signs token exchange requests with `private_key_jwt` (RFC 7523)

Register the Worker’s JWKS URL in Keycard Console as the application’s public key endpoint.

`createKeycardWorker` auto-detects which mode to use based on which env vars are set.

## Environment Variables

| Variable                | Required           | Description                 |
| ----------------------- | ------------------ | --------------------------- |
| `KEYCARD_ISSUER`        | Yes                | Keycard zone URL            |
| `KEYCARD_CLIENT_ID`     | Option A           | Application client ID       |
| `KEYCARD_CLIENT_SECRET` | Option A           | Application client secret   |
| `KEYCARD_PRIVATE_KEY`   | Option B           | PEM-encoded RSA private key |
| `KEYCARD_RESOURCE_URL`  | For token exchange | Upstream resource URL       |

## Related

SEE ALSO

[MCP PackageExpress-based equivalent with the same auth primitives](/sdk/mcp/index.md)[OAuth PackageLow-level OAuth primitives used by this package](/sdk/oauth/index.md)

## Source

[TypeScript SDK@keycardai/cloudflare](https://github.com/keycardai/typescript-sdk/tree/main/packages/cloudflare)[ExampleCloudflare Worker with MCP tools and token exchange](https://github.com/keycardai/typescript-sdk/tree/main/examples/cloudflare-worker)
