Skip to main content
Integrations

Authentication Chain

End-to-end auth flow from browser through Cloudflare Access, middleware, API route validation, and backend credential injection

February 23, 2026

Authentication Chain

Every authenticated request to Arcturus-Prime passes through five stages before it reaches a backend service. Each stage adds a layer of verification or credential injection, and no single stage can be bypassed without breaking the chain. This document traces a request from the browser all the way to the backend.

The 5-Stage Auth Flow

Browser ──> CF Edge ──> CF Pages Middleware ──> API Route Handler ──> Backend Proxy
  (1)         (2)            (3)                     (4)                  (5)
  1. Browser sends a request to Arcturus-Prime.com. Cloudflare’s edge network intercepts it before it reaches the origin.
  2. Cloudflare Access enforces an Access policy at the edge, authenticating the user and injecting a signed JWT into the request headers.
  3. Astro middleware on CF Pages captures the Cloudflare runtime environment and blocks protected routes on unauthorized hosts.
  4. API route handlers call validateAdmin() or resolveAuthState() to verify the JWT, check the user’s role in KV, and enforce permissions.
  5. Backend proxy routes inject server-side credentials (Bearer tokens, API keys) before forwarding the request to internal services. The browser never sees these secrets.

Stage 1-2: Cloudflare Access at the Edge

Cloudflare Access is managed at Arcturus-Prime.cloudflareaccess.com. Admin paths (/admin/*) and sensitive services are protected by Access policies that run at the CDN edge — unauthorized requests are rejected before they ever reach the origin.

When a user authenticates through the Access login screen, Cloudflare sets two cookies and injects headers on every subsequent request:

ArtifactTypePurpose
Cf-Access-Jwt-AssertionHeaderSigned RS256 JWT containing user email, audience, issuer, and expiry
Cf-Access-Authenticated-User-EmailHeaderPlaintext email (convenience; always cross-checked against the JWT)
CF_AuthorizationCookieSame JWT, used as a fallback when the header is absent
CF_AppSessionCookieOpaque session token for Cloudflare’s newer session management

The JWT is signed with Cloudflare’s private key. Verification uses the JWKS endpoint at https://{team-domain}/cdn-cgi/access/certs. Arcturus-Prime caches the JWKS keys in memory for 5 minutes and caches imported CryptoKey objects indefinitely to avoid repeated key imports.

Stage 3: Astro Middleware

The Astro middleware (src/middleware.ts) runs on every SSR request. Its auth-related responsibilities are:

  • Capture the Cloudflare runtimesetRuntimeEnv(runtime.env) makes KV bindings, environment variables, and secrets available to downstream code via getEnv() and getKV().
  • Block protected routes on preview deployments/admin, /auth, /user, and /dashboard paths return 403 on any host not in the allowed set (Arcturus-Prime.com, www.Arcturus-Prime.com, localhost). This prevents accidental exposure on *.pages.dev preview URLs.
  • Canonicalize hostnames — Requests on *.pages.dev are redirected to Arcturus-Prime.com with a 308 to prevent search engine duplication.

The middleware does not perform JWT validation itself. That responsibility is delegated to the individual API route handlers and the @Arcturus-Prime/edge-toolkit auth package, which gives each route control over its own auth strategy.

Stage 4: API Route Auth

API routes use two primary functions from @Arcturus-Prime/edge-toolkit:

validateAdmin(request)

Used by admin-only routes (content management, user roles, pentest tools, build swarm control). Checks in order:

  1. Dev mode bypass — If import.meta.env.DEV is true, returns { authenticated: true, email: 'dev@localhost' } immediately.
  2. JWT verification — Reads the Cf-Access-Jwt-Assertion header (falling back to the CF_Authorization cookie). Verifies the RS256 signature against the JWKS, checks exp/nbf timestamps, validates iss against CF_ACCESS_TEAM_DOMAIN, and confirms aud matches CF_ACCESS_AUD. Then checks the verified email against the ADMIN_EMAILS environment variable.
  3. CF_AppSession fallback — If no JWT is present, calls Cloudflare’s get-identity endpoint with the CF_AppSession cookie to resolve the user email, then checks against ADMIN_EMAILS. Session identity results are cached for 3 minutes.

If none of these succeed, the request gets a 401 via unauthorizedResponse().

resolveAuthState(request)

Used by user-facing routes (/api/auth/me, login page, user portal). Same identity resolution as above, but instead of a binary admin check, it resolves the full user profile:

  1. Verify identity via JWT or CF_AppSession.
  2. Look up the email in the KV user store (data:user-roles) for role, display name, permitted services, and feature flags.
  3. If not in KV, fall back to the ADMIN_EMAILS env var (grants admin role).
  4. If authenticated via CF Access but not in the user store at all, grant demo role as a safe default.

Role System

RolePermissionsUse Case
admin['*'] — unrestrictedFull access to all routes, APIs, and management tools. Controlled exclusively by the ADMIN_EMAILS env var; cannot be assigned via the KV user management API.
memberdashboard:view, services:view, services:manage, command:view, profile:view, portal:view, portal:deploy, portal:edit-code, portal:workbenchUser portal, dashboard, site deployment, and limited service management. Assigned via the roles API.
demoadmin:view, command:view, profile:viewRead-only tour of the admin panel. Auto-assigned to any CF Access user not found in the KV store.

Permission checks use hasPermission(auth, scope) which returns true if the user’s permissions array includes '*' or the exact scope string. The requireAdmin() type guard narrows the auth state to confirm role === 'admin'.

Stage 5: Backend Credential Injection

The @Arcturus-Prime/astro-proxy package provides a createProxy() factory that builds API route handlers with declarative config. When a proxy route forwards a request to a backend service, it injects server-side headers read from Cloudflare environment bindings:

headers: {
  'Authorization': { env: 'TITAN_ADMINBOX_TOKEN', format: 'bearer' },
  'X-Api-Key':     { env: 'SWARM_ADMIN_KEY' },
}

The proxy resolves the backend URL from environment variables (production) or hardcoded LAN addresses (dev mode), builds the appropriate auth headers using getEnv(), and forwards the request. The browser never sees any backend credential — tokens like MM_ARGOBOX_TOKEN, TITAN_ADMINBOX_TOKEN, and SWARM_ADMIN_KEY exist only in the Cloudflare Pages runtime bindings and are injected server-side at the edge.

The proxy also supports three auth strategies per route: 'none' (public), 'admin' (always require admin), and 'readOpen' (GET/HEAD are public, mutations require admin). This lets read-heavy dashboards serve data without authentication while still protecting write operations.

Login and Logout

Login (/auth/login) — Checks if the user is already authenticated via resolveAuthState(). If not, redirects to /admin/auth-bounce, which sits behind a Cloudflare Access policy. CF Access intercepts, shows the login screen, and after authentication redirects back through the bounce page to the original destination. The redirect target is validated to prevent open-redirect attacks (must start with / and not //).

Logout (/auth/logout) — Clears both CF_Authorization and CF_AppSession cookies by setting them to epoch expiry, then redirects to the Cloudflare Access logout endpoint (/cdn-cgi/access/logout) to kill the SSO session. Without the SSO logout, clicking login again would silently re-authenticate.

authcloudflare-accessjwtmiddlewaresecurity