This repository has no description
flarebot docs oauth-onboarding.md
18 kB

Cloudflare connection and publisher setup #

Flarebot has two independent Workers. control-plane/index.ts serves public onboarding, Cloudflare OAuth, account selection and protected authorization storage. worker/index.ts is the customer runtime. Its deployment release never includes the control plane, OAuth secrets or session/grant storage. The public Octane/Kumo screen does not connect to customer Agent transports or load private conversations, memory, model keys or agent content.

Build the customer first (pnpm build:release), then run pnpm build:control-plane. The latter emits its own client/server outputs and Wrangler dry-run artifact under dist/control-plane/. Use wrangler.control-plane.jsonc only for the publisher Worker; never install this bundle in a customer's account. Builds and packaged tests must run sequentially. A fresh customer build can replace dist outputs, so rebuild the control plane after it. pnpm test:oauth requires both artifacts and Playwright Chromium.

Publisher prerequisites #

Create a Flarebot-owned OAuth client using Cloudflare's client registration. Never reuse Wrangler's OAuth client or developer credentials. Register the exact HTTPS callback https://<publisher-origin>/auth/callback, Authorization Code with PKCE S256, and the actual registered token authentication method: none, client_secret_basic, or client_secret_post. A confidential method requires FLAREBOT_OAUTH_CLIENT_SECRET; none rejects that binding. Private clients admit parent-account members; public onboarding additionally requires publisher domain verification and public promotion.

The publisher must review the authenticated OAuth scopes catalog (GET https://api.cloudflare.com/client/v4/oauth/scopes, official SDK client.iam.oauthScopes.list()). Catalog id values are authorization scope IDs; underlying resource strings in scopes are supporting evidence, not OAuth IDs. Retain the actual catalog name/category/resource strings in the nonsecret capability manifest. Use the smallest available scopes that cover every operation below. Do not convert Wrangler colon scopes into guessed dot IDs or silently request all permissions.

Flarebot capability Required operation and catalog review
identity Verified UserInfo sub from the registered code flow. Check the client's normalized identity scopes and real UserInfo behavior.
account-selection Enumerate the grant's accounts. account.read is the documented Account Read example.
worker-upload Upload the Worker, SQLite Durable Object exports and native AI/BROWSER/LOADER binding declarations. API permission: Workers Scripts Write.
asset-upload Create the static asset upload session; bytes use its upload JWT. API permission: Workers Scripts Write.
workers-dev Enable the Worker endpoint and inspect account subdomain. API permission: Workers Scripts Write.
container-application Create/read/modify the Sandbox container application. Verify the catalog mapping for Workers Containers Write / Containers Write.
container-rollout Create and observe the Sandbox application rollout. Verify the exact catalog coverage.
model-gateway Read/create the default AI Gateway. Requires AI Gateway Write (aig.write); no custom-provider route is needed.

workers-platform.read and workers-platform.write are publicly documented examples, but public documentation does not establish that the write scope is the minimal complete scope for Scripts and Containers. No live catalog/client verification or deployment was performed for this implementation. There is no production default pretending that these examples cover the whole artifact.

The workflow creates default before Worker upload on install and upgrade. OpenRouter uses AI Gateway's native openrouter provider, so Flarebot does not create or manage a custom-provider route. Enabling it still uses an ownership-checked, purpose-separated signed receipt and ensures the default gateway exists. The model-gateway capability and aig.write grant are still required for gateway management, not for forwarding inference.

POST /auth/start also accepts a single providerInstallationId (32 hex digits) instead of returnTo. The encrypted OAuth transaction preserves only that ID; callback returns to the fixed local provider setup view. No URL, account or owner is accepted from the browser. GET /api/installations/:id/providers/openrouter checks ownership without enabling. Its exact-Origin-protected POST rechecks ownership, gateway-management scope and account membership, ensures the default gateway, then notifies the pinned customer runtime with a purpose-separated signed receipt. It returns the registered runtime's /settings URL only after acknowledgment. Provider keys never pass through these endpoints. Expired grants require reconnect; provider enablement is persisted in the runtime independently of grant lifetime.

No zone/DNS route changes, registry image pushes, Cloudflare inference REST calls, Browser REST probes or extra customer OAuth-client administration are needed by this path. Merely declaring native bindings does not establish a need for extra AI/browser scopes; managing the default gateway requires aig.write. The existing public, pinned Sandbox image needs no registry push permission. Account listing proves account visibility, never deployment write permissions, product entitlement or container readiness. A real authorized full-artifact installation is still required to certify the reviewed mapping.

Optional custom-domain setup adds separately reviewed domain-zones and domain-routing capabilities. They are not required for ordinary installation or sign-in. The domain flow accepts domainInstallationId instead of providerInstallationId and returns only to the fixed Flarebot domain setup view after OAuth. Never infer the actual OAuth scope IDs from those capability names.

Configuration manifest #

Set FLAREBOT_MODE=control-plane, FLAREBOT_ENV=production, and a JSON string FLAREBOT_CONTROL_PLANE. Its schema version remains 1 with new explicit OAuth setup fields. The following is a template, deliberately not runnable: replace all catalog/client placeholders using the reviewed returned client configuration. oauthScopes and the manifest's scope IDs must be identical sets, and every requested scope must be allowed by registered client scopeIds. Additional registered permissions are not requested. Every requested scope must justify at least one required capability and all eight capabilities must be covered. Multiple capabilities may map to one scope. Do not add refresh/offline scopes; v0.1 reconnects for later privileged operations.

{
  "schemaVersion": 1,
  "publicOrigin": "https://control.example.com",
  "oauthClientId": "<registered-flarebot-client-id>",
  "oauthRedirectUri": "https://control.example.com/auth/callback",
  "oauthTokenAuthMethod": "client_secret_basic",
  "oauthScopes": ["<exact-reviewed-scope-ids>"],
  "oauthCapabilities": {
    "schemaVersion": 1,
    "artifactVersion": "0.1.0-dev.1",
    "reviewedAt": "2026-09-06",
    "registeredClient": {
      "clientId": "<registered-flarebot-client-id>",
      "redirectUri": "https://control.example.com/auth/callback",
      "tokenAuthMethod": "client_secret_basic",
      "scopeIds": ["<exact-normalized-registered-scope-ids>"],
      "userinfoVerified": true
    },
    "scopes": [
      {
        "id": "<actual-catalog-id-or-verified-identity-scope>",
        "name": "<actual-catalog-name>",
        "category": "<actual-category-if-present>",
        "resourceScopes": ["<actual-underlying-resource-scopes>"],
        "capabilities": ["<covered-capability-names-from-table>"]
      }
    ]
  }
}

Identity names are not inferred from OIDC discovery. Verify that the registered code-flow client actually supports UserInfo and its normalized requested scopes. The implementation does not consume an ID token or request implicit flow to force one. Its sole identity source is the sub in the authenticated HTTPS UserInfo response. It discards optional email/profile, refresh tokens and ID tokens.

The manifest is a publisher attestation with strict shape/coverage checks; the parser cannot authenticate the catalog evidence or prove that a supplied mapping is least privilege. Keep the dated catalog review with publisher configuration, review it when changing the release, and test real grants. Missing setup returns oauth_setup_required or oauth_capability_unavailable, with publisher recovery copy, before redirecting a customer to Cloudflare.

Every publisher version bump also requires reviewing the saved FLAREBOT_CONTROL_PLANE.oauthCapabilities and setting its artifactVersion to the new package version. keep_vars preserves the previous attestation; it does not make that attestation valid for the new release. Keep the registered client, scopes and secrets unchanged when the reviewed capabilities have not changed. After deployment, an unauthenticated GET /api/connection must return HTTP 401 with not_connected, not a publisher setup error. Verify that same-origin POST /auth/start returns HTTP 303 to Cloudflare's OAuth authorization endpoint; HTTP 200 from /connect alone only verifies the static page.

Provision the native SQLite AUTH_VAULT binding from wrangler.control-plane.jsonc. Set independent server secrets: FLAREBOT_SESSION_SECRET (existing configuration contract, at least 32 bytes), FLAREBOT_CREDENTIAL_ENCRYPTION_KEY (32 random bytes, unpadded base64url), and the client secret only for a confidential registered client. Session cookies use random opaque references rather than self-contained claims; the retained session secret is not an encryption substitute. Never put secrets in the manifest, frontend build variables, installation metadata, Workflow inputs/results or logs. Rotating the credential encryption key invalidates extant protected records; coordinate key rotation with reconnects. Unreadable sessions are rejected and can be replaced by fresh OAuth or retired on logout. If an old grant reference or token can no longer be decrypted, Flarebot cannot remotely revoke it; its bounded vault alarm and Cloudflare token expiry remain in force. Users can revoke the application in Cloudflare immediately. Keep the key stable across normal upgrades.

Browser and storage boundary #

POST /auth/start requires the exact configured Origin and an optional single returnTo=/connect. It creates independent 256-bit state, PKCE verifier and browser binding, stores a ten-minute encrypted transaction, and sends a Secure, HttpOnly, SameSite=Lax, Path=/ __Host-flarebot-oauth cookie. There are no arbitrary redirect/return destinations. One browser transaction is current at a time; starting a second flow replaces the binding cookie and invalidates the earlier browser flow. The earlier record expires automatically.

GET /auth/callback checks the fixed callback origin/path, exact single state and code (or error), matching browser binding and previous session, optional issuer, and transaction expiry. It rejects mixed/duplicate/malformed parameters and atomically claims/deletes the transaction before code exchange. Errors and network failures never reopen state. Repeated callbacks cannot exchange again. An authorization error consumes its valid transaction too.

All authorization, token, UserInfo, revoke and Accounts URLs are fixed to Cloudflare's documented endpoints. Credential-bearing requests use redirect: manual with explicit non-2xx rejection, ten-second deadlines and bounded response bodies. Token error categories are sanitized; no raw response, query, token, OAuth code, verifier or credential enters logs. The token response's scope is checked against the required manifest; when omitted, OAuth's same-as-requested scope rule applies. No opaque token decoding substitutes for reported scope. No refresh credential is retained.

Successful login creates a fresh eight-hour opaque identity session, deletes the previous session/grant, and attempts to revoke the previous grant. Credentials are retained for the lesser of the token lifetime and one hour. Identity sessions and grants use separate unguessable references and separate DO records. All record payloads are AES-GCM encrypted with associated data binding ciphertext to DO ID, record kind and expiry. Native transactions serialize state claims and session retirement; native alarms remove expired records. Grant records never share installation storage.

GET /api/connection returns account IDs/names, selected account ID and grant expiry only to the authenticated session. It enumerates all granted account pages, bounded to 20 pages of 50 accounts, and never exposes principal/grant references or tokens. POST /api/account requires the session plus exact Origin, validates the small form body, fetches the grant's accounts again and rejects forged selection. The UI escapes account names and never stores tokens in browser storage.

POST /auth/disconnect requires exact Origin, atomically retires the local session and removes its grant even after the session expires or publisher capability setup changes, then clears both cookies. It attempts remote revocation when setup allows; if unavailable, the UI explains that the user is locally signed out and can revoke Flarebot in Cloudflare authorized applications immediately. Credentials still expire at Cloudflare's token expiry. No unbounded retry queue is retained.

Responses are no-store. Callback/API responses use no-referrer; the public page uses same-origin so browser form POSTs retain the Origin header required for CSRF checks without disclosing referrers to Cloudflare. SSR is public generic markup with only an ASSETS binding. Callback and credential request logging is disabled by the separate Wrangler observability configuration. Do not enable upstream logs that capture callback query strings or authorization headers.

Recovery distinguishes revoked/expired authorization (reconnect), missing client capabilities (publisher fix), account denial/empty selection (choose/reconnect), and transient Cloudflare failure (retry). A fresh visitor sees ordinary connection onboarding. Product entitlement and actual deployment-write failures belong to the installer's checkpointed state, not an account-read preflight claim.

Integration seams and remaining issues #

authenticatedPrincipal(request, env) in control-plane/session.ts resolves the real opaque session to {subject, grantRef, selectedAccountId, expiresAt} for trusted server callers. selectedDeploymentGrant(request, env) requires selection and revalidates current grant/account visibility, returning the principal and short-lived credential only within a trusted operation call stack. Both are server-only; no caller-selected principal/token endpoint exists. The installation registry binds ownership metadata to this verified subject and stores no grant references or credentials. Every future install/update API must retain exact-Origin checks and look up ownership; authentication alone does not authorize an arbitrary record.

FLA-9 must wire the real ownership-verified login bridge to customer runtime: customer-generated challenge, exact pinned issuer/audience/installation/owner, short expiry, one-time jti, pinned public signing key, atomic customer consume, and a real customer session cookie. FLA-8 intentionally issues no customer owner session or fake installation-ready link. Customer createOwnerSession remains a server-only seam until that bridge is implemented. Preserve customer session secrets across upgrades because BYOK encryption derives from them.

Validation scope #

tests/oauth.test.mjs runs the actual Worker routing and native SQLite DOs with only an explicit fixed Cloudflare network fixture. It exercises PKCE/state replay races, cookie binding, expiry/alarms, rotation, restarts, error/redirect rejection, UserInfo identity, ciphertext/token separation, granted-account pagination and forgery, Origin checks, revoked/expired reconnect, public SSR, confidential methods, missing publisher setup, and real Chromium connect/select/disconnect rendering. The production bundle exports no fixture admin routes. Artifact assertions verify that OAuth secrets/modules stay outside customer and browser bundles.

These local checks do not certify real consent, catalog minimality, public-client promotion, account entitlements, Worker uploads or Containers boot. Those require the real publisher/client setup and an authorized test customer installation.