Skip to content

Repository files navigation

access-on-cf

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-cf when 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
Loading

What v0.1 provides

  • verification of Cf-Access-Jwt-Assertion with Cloudflare's remote JWKS;
  • strict RS256, issuer, audience, iat, nbf, and exp validation;
  • typed human and service-token principals;
  • a Hono middleware adapter;
  • a local mock identity that only works with CLOUDFLARE_ENV=dev and a loopback URL;
  • a deployable /whoami example 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.

Cloudflare plan and cost

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.

Quick start

1. Configure Cloudflare Access

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.

2. Install the package

Until the package is published to a registry, install it directly from the organization repository:

pnpm add github:Auditmos/access-on-cf

After a registry release, the equivalent command will be:

pnpm add @auditmos/access-auth

3. Create one verifier per Worker isolate

import { 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.

4. Configure Worker variables

{
  "vars": {
    "CLOUDFLARE_ENV": "production",
    "AUTH_MODE": "access",
    "CF_ACCESS_TEAM_DOMAIN": "https://your-team.cloudflareaccess.com",
    "CF_ACCESS_AUD": "your-application-audience-tag"
  }
}

These values are identifiers, not secrets. The login is enforced by the Access application attached to the service hostname.

Hono

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:

Local development

Copy .dev.vars.example to .dev.vars, leave AUTH_MODE=mock, and run:

pnpm dev

Open http://localhost:8787/whoami. Mock mode fails closed unless both conditions are true:

  1. CLOUDFLARE_ENV is exactly dev.
  2. 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.

Commands

pnpm install
pnpm verify       # typegen, types, tests, lint, build, dead-code check
pnpm dev          # run the reference /whoami Worker

Security model

The 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.

License

ISC

About

Optional Cloudflare Access authentication for Auditmos Cloudflare templates

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages