Authentication Chain
End-to-end auth flow from browser through Cloudflare Access, middleware, API route validation, and backend credential injection
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)
- Browser sends a request to
Arcturus-Prime.com. Cloudflare’s edge network intercepts it before it reaches the origin. - Cloudflare Access enforces an Access policy at the edge, authenticating the user and injecting a signed JWT into the request headers.
- Astro middleware on CF Pages captures the Cloudflare runtime environment and blocks protected routes on unauthorized hosts.
- API route handlers call
validateAdmin()orresolveAuthState()to verify the JWT, check the user’s role in KV, and enforce permissions. - 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:
| Artifact | Type | Purpose |
|---|---|---|
Cf-Access-Jwt-Assertion | Header | Signed RS256 JWT containing user email, audience, issuer, and expiry |
Cf-Access-Authenticated-User-Email | Header | Plaintext email (convenience; always cross-checked against the JWT) |
CF_Authorization | Cookie | Same JWT, used as a fallback when the header is absent |
CF_AppSession | Cookie | Opaque 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 runtime —
setRuntimeEnv(runtime.env)makes KV bindings, environment variables, and secrets available to downstream code viagetEnv()andgetKV(). - Block protected routes on preview deployments —
/admin,/auth,/user, and/dashboardpaths return 403 on any host not in the allowed set (Arcturus-Prime.com,www.Arcturus-Prime.com,localhost). This prevents accidental exposure on*.pages.devpreview URLs. - Canonicalize hostnames — Requests on
*.pages.devare redirected toArcturus-Prime.comwith 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:
- Dev mode bypass — If
import.meta.env.DEVis true, returns{ authenticated: true, email: 'dev@localhost' }immediately. - JWT verification — Reads the
Cf-Access-Jwt-Assertionheader (falling back to theCF_Authorizationcookie). Verifies the RS256 signature against the JWKS, checksexp/nbftimestamps, validatesissagainstCF_ACCESS_TEAM_DOMAIN, and confirmsaudmatchesCF_ACCESS_AUD. Then checks the verified email against theADMIN_EMAILSenvironment variable. - CF_AppSession fallback — If no JWT is present, calls Cloudflare’s
get-identityendpoint with theCF_AppSessioncookie to resolve the user email, then checks againstADMIN_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:
- Verify identity via JWT or
CF_AppSession. - Look up the email in the KV user store (
data:user-roles) for role, display name, permitted services, and feature flags. - If not in KV, fall back to the
ADMIN_EMAILSenv var (grants admin role). - If authenticated via CF Access but not in the user store at all, grant
demorole as a safe default.
Role System
| Role | Permissions | Use Case |
|---|---|---|
admin | ['*'] — unrestricted | Full 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. |
member | dashboard:view, services:view, services:manage, command:view, profile:view, portal:view, portal:deploy, portal:edit-code, portal:workbench | User portal, dashboard, site deployment, and limited service management. Assigned via the roles API. |
demo | admin:view, command:view, profile:view | Read-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.