# How Cloudflare Access JWT Authentication Works in OpenSEO

> Discover how Cloudflare Access JWT authentication integrates with OpenSEO. Learn to validate Cloudflare tokens and secure your applications effectively.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: deep-dive
- Published: 2026-09-10

---

**OpenSEO validates Cloudflare Access tokens by extracting the `cf-access-jwt-assertion` header and verifying it against Cloudflare's rotating JWKS endpoint using the `jose` library in [`src/middleware/ensure-user/cloudflareAccess.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/cloudflareAccess.ts).**

The OpenSEO repository (every-app/open-seo) implements identity-aware authentication through Cloudflare Access JWT, enabling zero-trust security for self-hosted deployments. When the `AUTH_MODE` environment variable is set to `cloudflare_access`, the application enforces strict JWT validation on every protected API route without requiring additional application-level authentication code.

## Authentication Architecture Overview

OpenSEO supports three distinct authentication modes configured via the `AUTH_MODE` environment variable. The system defaults to `cloudflare_access` for public-facing deployments, leveraging Cloudflare's reverse proxy to handle identity provider (IdP) integration upstream.

### Supported Authentication Modes

- **cloudflare_access**: Validates JWT tokens issued by Cloudflare Access. This mode requires the `TEAM_DOMAIN` and `POLICY_AUD` environment variables.
- **local_noauth**: Disables authentication entirely, intended strictly for local development environments.
- **hosted**: Delegates authentication to Better Auth, suitable for managed hosting scenarios where Cloudflare Access is unavailable.

When `AUTH_MODE=cloudflare_access`, the middleware chain in [`src/middleware/ensure-user/cloudflareAccess.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/cloudflareAccess.ts) intercepts all incoming requests before they reach server functions like those defined in [`src/serverFunctions/workspace.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/workspace.ts).

## JWT Verification Implementation

The verification logic follows the OpenID Connect (OIDC) pattern, using the `jose` library to handle cryptographic validation against Cloudflare's public key infrastructure.

### Extracting the Access Token

The middleware first extracts the JWT from the incoming request headers:

```typescript
// src/middleware/ensure-user/cloudflareAccess.ts
const token = req.headers.get("cf-access-jwt-assertion");
if (!token) {
  throw new Error("Missing Cloudflare Access token");
}

```

Cloudflare Access sends the token via the `cf-access-jwt-assertion` header by default, though the middleware can also parse it from cookies when necessary.

### Resolving the JWKS Endpoint

Using the `TEAM_DOMAIN` environment variable, the middleware constructs the JSON Web Key Set (JWKS) URL and creates a remote key set resolver:

```typescript
import { createRemoteJWKSet } from "jose";

const jwksUrl = new URL(`${env.TEAM_DOMAIN}/cdn-cgi/access/certs`);
const remoteJwks = createRemoteJWKSet(jwksUrl);

```

This endpoint (`https://<your-team>.cloudflareaccess.com/cdn-cgi/access/certs`) returns the rotating public keys used to sign all Access tokens for your organization.

### Verifying Token Claims

The middleware calls `jwtVerify` to validate the signature, expiration (`exp`), audience (`aud`), and issuer (`iss`) claims:

```typescript
import { jwtVerify, type JWTPayload } from "jose";

const { payload } = await jwtVerify(token, remoteJwks, {
  audience: env.POLICY_AUD,
  issuer: env.TEAM_DOMAIN,
});

// Attach verified payload to request for downstream use
(req as any).user = payload as JWTPayload;

```

The `POLICY_AUD` value must match the Audience tag configured in your Cloudflare Access application policy. Failed verification immediately rejects the request before it reaches business logic in files like [`src/serverFunctions/workspace.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/workspace.ts).

## Error Handling and Type Safety

OpenSEO centralizes error handling to provide actionable feedback when JWT validation fails.

### Mapping Verification Errors

The [`src/middleware/ensure-user/accessTokenErrors.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/accessTokenErrors.ts) file imports error classes from `jose` and maps them to user-friendly messages:

- **JWTExpired**: Returned when the token's `exp` claim has passed.
- **JWTClaimValidationFailed**: Triggered when audience or issuer claims do not match the configured environment variables.

This separation of concerns keeps the main verification logic in [`cloudflareAccess.ts`](https://github.com/every-app/open-seo/blob/main/cloudflareAccess.ts) clean while ensuring consistent error responses across the API surface.

### Type Definitions

Strong typing for the authenticated user context is defined in [`src/middleware/ensure-user/types.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/types.ts), which exports the `JWTPayload` interface used when attaching identity information to incoming requests.

## Configuration and Deployment

Enabling Cloudflare Access JWT authentication requires specific environment configuration before deployment.

### Required Environment Variables

Configure your production environment according to the template in `.env.example`:

```bash
AUTH_MODE=cloudflare_access
TEAM_DOMAIN=https://myteam.cloudflareaccess.com
POLICY_AUD=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxx

```

The `TEAM_DOMAIN` must include the protocol (`https://`) and match your Cloudflare Access organization URL exactly. The `POLICY_AUD` corresponds to the Application Audience (AUD) tag found in your Cloudflare Access dashboard under the specific application's configuration.

### Middleware Activation

Once configured, the middleware automatically applies to all API routes. No manual route protection is required, as the validation occurs in the request lifecycle before server functions execute.

## Consuming Authentication in Server Functions

Downstream code accesses the verified identity through the request object populated by the middleware.

```typescript
// src/serverFunctions/workspace.ts
export async function getWorkspace(req: Request) {
  const user = (req as any).user;
  if (!user?.email) {
    throw new Error("Unauthenticated");
  }
  
  // Proceed with workspace-scoped logic using user.email
  return { workspaceId: user.custom?.workspace_id };
}

```

The `JWTPayload` includes standard JWT claims plus Cloudflare-specific fields like `email` and custom claim data configured in your Access policies.

## Summary

- OpenSEO uses [`src/middleware/ensure-user/cloudflareAccess.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/cloudflareAccess.ts) to validate Cloudflare Access JWT tokens on every request when `AUTH_MODE=cloudflare_access`.
- The middleware extracts the `cf-access-jwt-assertion` header and verifies it against the JWKS endpoint at `https://<TEAM_DOMAIN>/cdn-cgi/access/certs` using `jose.createRemoteJWKSet`.
- Validation enforces the `POLICY_AUD` audience claim and `TEAM_DOMAIN` issuer claim to prevent token replay attacks.
- Errors are standardized in [`src/middleware/ensure-user/accessTokenErrors.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/accessTokenErrors.ts), handling specific `jose` exceptions like `JWTExpired` and `JWTClaimValidationFailed`.
- Verified payloads are attached to the request object for use in server functions such as [`src/serverFunctions/workspace.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/workspace.ts).

## Frequently Asked Questions

### What header carries the Cloudflare Access JWT in OpenSEO?

Cloudflare Access transmits the JWT in the `cf-access-jwt-assertion` HTTP header. The OpenSEO middleware specifically checks this header (with fallback support for cookies) in [`src/middleware/ensure-user/cloudflareAccess.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/cloudflareAccess.ts) before proceeding with cryptographic verification.

### How does OpenSEO handle expired or invalid tokens?

When `jose.jwtVerify` throws `JWTExpired` or `JWTClaimValidationFailed` errors, the middleware catches these exceptions and transforms them using the mapping logic in [`src/middleware/ensure-user/accessTokenErrors.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/accessTokenErrors.ts). This converts library-specific errors into standardized API responses indicating whether the token expired or failed claim validation.

### Can I run OpenSEO without Cloudflare Access?

Yes. Set `AUTH_MODE=local_noauth` for development environments to bypass all authentication checks, or use `AUTH_MODE=hosted` to integrate with Better Auth instead. However, production deployments behind Cloudflare should use `cloudflare_access` to leverage zero-trust security at the edge.

### Where are the TypeScript types for the JWT payload defined?

The `JWTPayload` interface and related authentication types are defined in [`src/middleware/ensure-user/types.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/types.ts). These types ensure type safety when accessing user identity information in server functions like [`src/serverFunctions/workspace.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/workspace.ts).