# OpenSEO Authentication Modes Explained: cloudflare_access, local_noauth, and hosted

> Discover OpenSEO's authentication modes: cloudflare_access, local_noauth, and hosted. Configure security easily with the AUTH_MODE variable for every-app/open-seo.

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

---

**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`](https://github.com/every-app/open-seo/blob/main/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 when `AUTH_MODE` is 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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/src/lib/selfhost-preflight.ts) module (lines 35-89) performs mode-specific checks:

```typescript
// 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

```bash
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

```bash
docker run \
  -e AUTH_MODE=local_noauth \
  -p 3000:3000 \
  everyapp/open-seo:latest

```

### Docker: Hosted SaaS mode

```bash
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`](https://github.com/every-app/open-seo/blob/main/src/lib/auth-mode.ts) module exports type-safe utilities for mode-aware logic:

```typescript
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`, and `hosted`.
- **Mode selection** happens via the `AUTH_MODE` environment variable, parsed by `getAuthMode()` in [`src/lib/auth-mode.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/auth-mode.ts).
- **Cloudflare Access** requires `TEAM_DOMAIN` and `POLICY_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.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/selfhost-preflight.ts) ensures 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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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`](https://github.com/every-app/open-seo/blob/main/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.