Small, opt-in Cloudflare Access integration for applications built from the Auditmos Cloudflare templates.
It gives internal users one organization login while each application remains free to choose its authentication strategy:
- choose
access-on-cfwhen the application is protected by Cloudflare Access; - keep Better Auth when the application owns public/customer accounts;
- choose neither for a public application.
This repository does not replace the organization's identity provider and does not store users, passwords, sessions, roles, or permissions. Cloudflare Access performs login and centralized allow/deny policy evaluation at the edge. This package verifies the signed Access assertion again inside the Worker and exposes a small, typed principal.
flowchart LR
U["Internal user"] --> A["Cloudflare Access"]
I["Organization IdP"] --> A
P["Central Access policies"] --> A
A -->|"signed JWT header"| W1["Hono / Worker app"]
A -->|"signed JWT header"| W2["TanStack Start app"]
A -->|"signed JWT header"| W3["Astro app"]
B["Better Auth app"] -. "independent option" .-> U
- verification of
Cf-Access-Jwt-Assertionwith Cloudflare's remote JWKS; - strict
RS256, issuer, audience,iat,nbf, andexpvalidation; - typed human and service-token principals;
- a Hono middleware adapter;
- a local mock identity that only works with
CLOUDFLARE_ENV=devand a loopback URL; - a deployable
/whoamiexample Worker; - integration recipes for Hono, TanStack Start, and Astro.
V0.1 intentionally leaves fine-grained, resource-level authorization inside each application. The central authorization boundary is the Cloudflare Access application policy: who may enter a given service. This keeps the shared mechanism small and avoids a second policy engine.
access-on-cf does not require a paid Cloudflare feature. A typical internal deployment can use
Cloudflare Zero Trust Free (up to 50 users) and Workers Free, provided the applications stay within
the Workers usage and CPU limits. The package requires no database or paid storage product.
Paid Cloudflare plans become relevant when the organization exceeds 50 Zero Trust users, exceeds Workers Free limits, or needs paid support, SLA, longer log retention, or Enterprise controls. Licensing for an external identity provider such as Okta or Microsoft Entra ID is separate from Cloudflare pricing.
See Cost and plan requirements for current limits, upgrade triggers, and official pricing links. Pricing was last verified on 2026-08-07 and should be checked again before a purchase decision.
Create a self-hosted Access application for the service hostname and attach the organization's
allow/deny policies. Copy its Application Audience (AUD) tag and the team's Access domain. See
the setup checklist.
Until the package is published to a registry, install it directly from the organization repository:
pnpm add github:Auditmos/access-on-cfAfter a registry release, the equivalent command will be:
pnpm add @auditmos/access-authimport { createAccessVerifier } from "@auditmos/access-auth";
const access = createAccessVerifier({
teamDomain: env.CF_ACCESS_TEAM_DOMAIN,
audience: env.CF_ACCESS_AUD,
});
const principal = await access.resolve(request);
if (principal.kind === "human") {
console.log(principal.subject, principal.email);
} else {
console.log(principal.serviceId);
}The verifier instance reuses jose's remote JWKS cache. Do not create it for every request.
These values are identifiers, not secrets. The login is enforced by the Access application attached to the service hostname.
import { env } from "cloudflare:workers";
import { Hono } from "hono";
import { createAccessVerifier } from "@auditmos/access-auth";
import { type AccessHonoEnv, requireAccess } from "@auditmos/access-auth/hono";
const access = createAccessVerifier({
teamDomain: env.CF_ACCESS_TEAM_DOMAIN,
audience: env.CF_ACCESS_AUD,
});
const app = new Hono<AccessHonoEnv<Env>>();
app.use("/internal/*", requireAccess<Env>(access));
app.get("/internal/me", (c) => c.json(c.get("accessPrincipal")));
export default app;More recipes:
- Hono and plain Workers
- TanStack Start
- Astro
- Choosing Access or Better Auth
- Cost and plan requirements
Copy .dev.vars.example to .dev.vars, leave AUTH_MODE=mock, and run:
pnpm devOpen http://localhost:8787/whoami. Mock mode fails closed unless both conditions are true:
CLOUDFLARE_ENVis exactlydev.- The request URL uses
localhost,127.0.0.1, or[::1].
To test a real Access token locally, use AUTH_MODE=access and send a valid
Cf-Access-Jwt-Assertion header. Never put assertion tokens in committed files or logs.
pnpm install
pnpm verify # typegen, types, tests, lint, build, dead-code check
pnpm dev # run the reference /whoami WorkerThe package trusts only a configured HTTPS team origin, fetches keys from its
/cdn-cgi/access/certs endpoint, accepts only RS256, and validates the application audience. An
unexpected or ambiguous identity is denied. HTTP error responses are deliberately generic.
See SECURITY.md for the trust boundary and vulnerability reporting guidance.
{ "vars": { "CLOUDFLARE_ENV": "production", "AUTH_MODE": "access", "CF_ACCESS_TEAM_DOMAIN": "https://your-team.cloudflareaccess.com", "CF_ACCESS_AUD": "your-application-audience-tag" } }