OpenSEO Authentication Modes Explained: cloudflare_access, local_noauth, and hosted
OpenSEO supports three authentication modes—cloudflare_access (default), local_noauth, and hosted—configured via the AUTH_MODE environment variable.
The OpenSEO repository provides flexible deployment options for different security and hosting requirements. Whether you're running a SaaS-style deployment on Cloudflare Workers, a private self-hosted instance, or the official managed service, the authentication system adapts through a single configuration point. This article examines how each mode works, where the logic lives in the codebase, and how to configure them correctly.
Authentication Mode Definitions
The complete list of supported modes is defined in src/lib/auth-mode.ts (lines 3-7). These three string literals form the foundation of OpenSEO's auth architecture:
cloudflare_access— Cloudflare Access OAuth integration. This is the default whenAUTH_MODEis unset or invalid.local_noauth— No built-in authentication; relies on external security (VPN, reverse proxy, or local-only access).hosted— Full SaaS mode with built-in authentication UI and billing integration.
The getAuthMode() helper parses the environment variable and enforces these values, with fallback logic in lines 15-30 that logs invalid values once and defaults to cloudflare_access for fail-closed behavior.
Mode 1: cloudflare_access (Default)
cloudflare_access is designed for production deployments on Cloudflare's edge network. It delegates authentication to Cloudflare Access, which presents a branded OAuth login page managed through your Cloudflare account.
Required environment variables
| Variable | Purpose |
|---|---|
TEAM_DOMAIN |
Your Cloudflare Access team domain |
POLICY_AUD |
The Access policy audience identifier |
Implementation details
In src/server.ts (lines 46-73), the server entry point routes requests based on the resolved auth mode. For cloudflare_access, the MCP (Machine-Control-Protocol) endpoint at /mcp receives Cloudflare Access identity headers. The transport layer in src/server/mcp/transport.ts (lines 55-70) resolves these headers into a typed user identity.
Certain features are exclusive to this mode. Workspace merging in src/server/auth/workspace-merge.ts (line 51) returns HTTP 403 if called in any other mode, as this operation requires Cloudflare's identity verification guarantees.
Mode 2: local_noauth
local_noauth removes all authentication requirements from the application layer. This mode assumes you've secured the instance through other means—private network access, VPN tunneling, or an upstream reverse proxy with its own auth.
When to use this mode
- Local development environments
- Single-admin deployments behind corporate firewalls
- Situations where you prefer to handle authentication at the infrastructure layer (e.g., Tailscale, WireGuard, or nginx basic auth)
Identity handling
The MCP transport in src/server/mcp/transport.ts generates a delegated local identity rather than resolving an external provider. This allows the rest of the application to function without conditional checks for user objects throughout the codebase.
Self-host preflight checks in src/lib/selfhost-preflight.ts (lines 35-89) validate that no conflicting auth variables are present, ensuring clean configuration state.
Mode 3: hosted
hosted enables the complete SaaS experience with OpenSEO's built-in authentication UI. This mode powers the official cloud offering and is available for those running their own multi-tenant deployments.
Required environment variables
| Variable | Purpose |
|---|---|
BETTER_AUTH_URL |
Base URL for the Better Auth service |
BETTER_AUTH_SECRET |
Shared secret for token validation |
GOOGLE_CLIENT_ID |
OAuth client for Google Search Console integration |
GOOGLE_CLIENT_SECRET |
Corresponding OAuth secret |
Runtime differences
Hosted mode activates additional server routes and client-side UI components. The server entry point enables full OAuth provider flows, and the isHostedAuthMode() predicate guards features like:
- Autumn billing webhooks
- Built-in signup/login pages
- Multi-workspace management UI
On the client, isHostedClientAuthMode() (lines 36-43 in src/lib/auth-mode.ts) determines which auth UI to render without requiring a server round-trip, enabling faster initial page loads.
Configuration Validation and Fail-Fast Behavior
OpenSEO validates auth configuration at startup to prevent runtime failures. The src/lib/selfhost-preflight.ts module (lines 35-89) performs mode-specific checks:
// Simplified excerpt from selfhost-preflight.ts
export function validateAuthConfig(mode: AuthMode, env: Env): void {
if (mode === 'cloudflare_access') {
assert(env.TEAM_DOMAIN, 'TEAM_DOMAIN required for cloudflare_access');
assert(env.POLICY_AUD, 'POLICY_AUD required for cloudflare_access');
}
if (mode === 'hosted') {
assert(env.BETTER_AUTH_URL, 'BETTER_AUTH_URL required for hosted');
assert(env.BETTER_AUTH_SECRET, 'BETTER_AUTH_SECRET required for hosted');
// Google OAuth optional but warned if missing
}
// local_noauth requires no additional variables
}
Validation errors emit clear messages and terminate the process, following the fail-fast principle for containerized deployments.
Practical Configuration Examples
Docker: Cloudflare Access deployment
docker run \
-e AUTH_MODE=cloudflare_access \
-e TEAM_DOMAIN=myteam.cloudflareaccess.com \
-e POLICY_AUD=abcdefghijklmnopqrstuvwxyz1234567890abcdef12345678 \
-p 3000:3000 \
everyapp/open-seo:latest
Docker: Local no-auth for development
docker run \
-e AUTH_MODE=local_noauth \
-p 3000:3000 \
everyapp/open-seo:latest
Docker: Hosted SaaS mode
docker run \
-e AUTH_MODE=hosted \
-e BETTER_AUTH_URL=https://auth.myopenseo.com \
-e BETTER_AUTH_SECRET=$(openssl rand -hex 32) \
-e GOOGLE_CLIENT_ID=1234567890-abc123def456.apps.googleusercontent.com \
-e GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxxxxx \
-p 3000:3000 \
everyapp/open-seo:latest
Detecting Auth Mode in Application Code
The src/lib/auth-mode.ts module exports type-safe utilities for mode-aware logic:
import { getAuthMode, isHostedAuthMode } from "@/lib/auth-mode";
export async function handleRequest(request: Request, env: Env) {
const authMode = getAuthMode(env.AUTH_MODE);
if (isHostedAuthMode(authMode)) {
// Enable billing webhooks, hosted UI routes
return hostedHandler(request, env);
}
if (authMode === "cloudflare_access") {
// Verify Cloudflare Access JWT
return cloudflareAccessHandler(request, env);
}
// local_noauth: skip verification, use synthetic identity
return unauthenticatedHandler(request, env);
}
Client components use the build-time isHostedClientAuthMode() helper to conditionally render auth UI without hydration mismatches.
Summary
- Three authentication modes are supported:
cloudflare_access(default),local_noauth, andhosted. - Mode selection happens via the
AUTH_MODEenvironment variable, parsed bygetAuthMode()insrc/lib/auth-mode.ts. - Cloudflare Access requires
TEAM_DOMAINandPOLICY_AUD; ideal for edge-deployed production workloads. - Local no-auth removes authentication entirely; suitable for development or privately-networked instances.
- Hosted mode activates full SaaS features including billing and built-in auth UI; requires
BETTER_AUTH_URL,BETTER_AUTH_SECRET, and optional Google OAuth credentials. - Preflight validation in
src/lib/selfhost-preflight.tsensures configurations are complete before the server accepts traffic.
Frequently Asked Questions
What is the default authentication mode if AUTH_MODE is not set?
cloudflare_access is the default. The getAuthMode() function in src/lib/auth-mode.ts (lines 15-30) falls back to this value when the environment variable is missing, empty, or contains an unrecognized string. Invalid values are logged once to assist debugging.
Can I switch authentication modes without rebuilding the container?
Yes. AUTH_MODE is read at runtime from environment variables, not baked into the build. Restart the container with the new value and validated credentials. The preflight checker in src/lib/selfhost-preflight.ts will verify the new configuration before accepting requests.
Why does workspace merging only work with cloudflare_access?
Workspace merging involves destructive data operations across user boundaries. The implementation in src/server/auth/workspace-merge.ts (line 51) explicitly checks for cloudflare_access mode because Cloudflare Access provides verified identity headers that prevent spoofing. local_noauth lacks this verification guarantee, and hosted mode uses a different identity model where workspace boundaries are managed through the Better Auth system rather than merged.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →