How Cloudflare Access JWT Authentication Works in OpenSEO: Complete Technical Guide

Cloudflare Access JWT authentication in OpenSEO extracts and verifies tokens via the cf-access-jwt-assertion header using the jose library, validates signatures against Cloudflare's JWK set, and builds an EnsuredUserContext for server-side authorization.

OpenSEO supports three authentication modes, one of which is cloudflare_access. This mode enables organizations to leverage Cloudflare Access as their identity provider, using industry-standard JWTs to authenticate users and authorize requests across the platform. According to the OpenSEO source code, the implementation relies on middleware-based token extraction, cryptographic verification via remote JWK sets, and explicit auth mode guards in protected server functions.

Cloudflare Access JWT Authentication Flow

The authentication process follows a strict five-step pipeline implemented in src/middleware/ensure-user/cloudflareAccess.ts. Each step handles a specific security concern, from header extraction to error classification.

Step 1: Extract the JWT from Request Headers

When Cloudflare Access protects an application, it automatically injects the cf-access-jwt-assertion header into every authenticated request. The middleware reads this header first:

const token = request.headers.get("cf-access-jwt-assertion");
if (!token) throw new AppError("Missing Cloudflare Access token");

Missing tokens trigger an immediate failure—there is no fallback to other authentication methods in cloudflare_access mode.

Step 2: Resolve and Validate the JWK Set

The middleware validates the team domain via validateTeamDomain, then constructs the well-known JWK endpoint:

const teamDomain = validateTeamDomain(env.TEAM_DOMAIN);
const jwks = createRemoteJWKSet(new URL(`${teamDomain}/.well-known/jwks.json`));

The createRemoteJWKSet function from the jose library fetches Cloudflare Access's public signing keys. These keys rotate periodically, and the remote JWK set handles caching and automatic refresh transparently.

Step 3: Cryptographic Token Verification

The core verification uses jwtVerify from jose:

const { payload } = await jwtVerify(token, jwks);

This single call validates:

  • Signature authenticity against the JWK set
  • Token expiration (exp claim)
  • Audience restriction (aud claim matches your Access application)
  • Issuer verification (tokens issued by your Cloudflare Access team domain)

Step 4: Error Classification with AppError

Verification failures map to structured errors via classifyAccessVerificationError in src/middleware/ensure-user/accessTokenErrors.ts. Common failure modes include:

Error Condition Mapped AppError
Missing or unreachable JWK set Connectivity/Configuration error
Malformed JWT payload Invalid token format
Expired token Authentication expired
Invalid signature Token tampering detected

This classification ensures upstream handlers receive consistent, actionable error information.

Step 5: Build the EnsuredUserContext

On successful verification, the middleware constructs the context object used throughout OpenSEO:

return {
  userId: payload.sub as string,           // Cloudflare Access user UUID
  organizationId: payload["org_id"] as string,  // Delegated workspace or "cloudflare"
  authMode: "cloudflare_access",
};

This EnsuredUserContext becomes the source of truth for all subsequent authorization decisions.

Auth Mode Integration and Guards

OpenSEO uses explicit auth mode checking to prevent authentication bypass. The src/lib/auth-mode.ts module defines three supported modes:

// Supported auth modes: cloudflare_access, local_noauth, hosted
export type AuthMode = "cloudflare_access" | "local_noauth" | "hosted";
export function getAuthMode(mode: string): AuthMode { ... }

Environment-Based Mode Selection

The env.AUTH_MODE environment variable determines which authentication path is active. Server functions check this before executing Cloudflare Access-specific logic:

import { env } from "cloudflare:workers";
import { getAuthMode } from "@/lib/auth-mode";

export async function workspaceMergeHandler(req: Request) {
  if (getAuthMode(env.AUTH_MODE) !== "cloudflare_access") {
    throw new AppError("Workspace merge is only available in cloudflare_access auth mode.");
  }
  // Proceed with Cloudflare Access authentication...
  const ctx = await ensureCloudflareUser(req);
}

Protected Operations in OpenSEO

Several critical operations enforce cloudflare_access mode:

Operation Source File Guard Purpose
Workspace merge src/server/auth/workspace-merge.ts Ensures workspace ownership verification via Cloudflare identity
MCP transport initialization src/server/mcp/transport.ts Resolves correct user context for model context protocol sessions
Legacy workspace functions src/serverFunctions/workspace.ts Per-user workspace isolation

Complete Middleware Implementation Reference

The core authentication logic in src/middleware/ensure-user/cloudflareAccess.ts integrates all components:

import { env } from "cloudflare:workers";
import { createRemoteJWKSet, jwtVerify } from "jose";
import { AppError } from "@/server/lib/errors";
import { validateTeamDomain } from "@/shared/selfhost-checks";

export async function ensureCloudflareUser(request: Request) {
  // Extract token from Cloudflare Access header
  const token = request.headers.get("cf-access-jwt-assertion");
  if (!token) throw new AppError("Missing Cloudflare Access token");

  // Resolve team domain and remote JWK set
  const teamDomain = validateTeamDomain(env.TEAM_DOMAIN);
  const jwks = createRemoteJWKSet(new URL(`${teamDomain}/.well-known/jwks.json`));

  // Verify and parse the JWT
  try {
    const { payload } = await jwtVerify(token, jwks);
    return {
      userId: payload.sub as string,
      organizationId: payload["org_id"] as string,
      authMode: "cloudflare_access",
    };
  } catch (e) {
    // Map jose errors to application-specific errors
    throw classifyAccessVerificationError(e);
  }
}

Dependency: JWK Set Fetching

The remote JWK set mechanism eliminates manual key management:

const jwks = createRemoteJWKSet(new URL(`${teamDomain}/.well-known/jwks.json`));

Cloudflare Access rotates signing keys regularly. The jose library handles HTTP caching headers, key ID (kid) matching, and automatic refresh when unknown key IDs appear.

Key Source Files and Responsibilities

File Path Component Responsibility
src/middleware/ensure-user/cloudflareAccess.ts Core middleware JWT extraction, verification, context building
src/middleware/ensure-user/accessTokenErrors.ts Error mapping Translates jose verification failures to AppError
src/lib/auth-mode.ts Mode definitions AuthMode type, getAuthMode() validation
src/server/mcp/transport.ts MCP integration Auth mode-aware transport initialization
src/server/auth/workspace-merge.ts Protected operation Example of mode-guarded business logic
src/serverFunctions/workspace.ts Legacy guards Per-user workspace authorization

Security Considerations

Token binding to request context — The cf-access-jwt-assertion header is injected by Cloudflare's edge network and cannot be forged by end users. Your origin server must run behind Cloudflare Access for this security property to hold.

Team domain validation — The validateTeamDomain function ensures only properly formatted Cloudflare Access domains are used, preventing JWK set redirection attacks.

No local fallback — Unlike local_noauth mode, cloudflare_access mode has no bypass. Missing or invalid tokens always reject the request.

Summary

  • Cloudflare Access JWT authentication in OpenSEO uses the cf-access-jwt-assertion header with cryptographic verification via the jose library
  • src/middleware/ensure-user/cloudflareAccess.ts orchestrates token extraction, remote JWK resolution, and context building
  • env.AUTH_MODE controls which authentication path is active; cloudflare_access enables the full JWT verification pipeline
  • Protected operations explicitly check getAuthMode(env.AUTH_MODE) before executing Cloudflare Access-specific logic
  • Error classification in src/middleware/ensure-user/accessTokenErrors.ts provides structured failure handling for debugging and user feedback

Frequently Asked Questions

What header does Cloudflare Access use for JWT authentication?

Cloudflare Access adds the cf-access-jwt-assertion header to every authenticated request. OpenSEO's middleware specifically looks for this header in src/middleware/ensure-user/cloudflareAccess.ts. Missing headers trigger an immediate AppError with message "Missing Cloudflare Access token".

How does OpenSEO verify the Cloudflare Access JWT signature?

OpenSEO uses createRemoteJWKSet from the jose library to fetch public signing keys from <team-domain>/.well-known/jwks.json. The jwtVerify function then validates the token signature, expiration, audience, and issuer against these keys. This approach handles automatic key rotation without configuration changes.

Can I use Cloudflare Access authentication in local development?

The cloudflare_access mode requires env.TEAM_DOMAIN to be set to a valid Cloudflare Access team domain with reachable JWK endpoints. For local development without Cloudflare Access, OpenSEO provides local_noauth mode defined in src/lib/auth-mode.ts. The hosted mode offers a third alternative for managed deployments.

What happens when JWT verification fails in OpenSEO?

Verification failures route through classifyAccessVerificationError in src/middleware/ensure-user/accessTokenErrors.ts. This function maps jose-specific errors (JWS verification failed, JWT expired, JWT malformed) to structured AppError instances that upstream handlers can catch and respond to appropriately.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →