Skip to content

feat: Cloudflare JWT Assertion Authentication Method#178

Open
jeremyhart wants to merge 9 commits into
gluk-w:mainfrom
jeremyhart:main
Open

feat: Cloudflare JWT Assertion Authentication Method#178
jeremyhart wants to merge 9 commits into
gluk-w:mainfrom
jeremyhart:main

Conversation

@jeremyhart

Copy link
Copy Markdown
Contributor

Enables authentication with Cloudflare JWT Assertion for use when the control plane is behind Cloudflare Zero Trust Application management

claude and others added 9 commits June 23, 2026 23:46
Adds an optional auth mode that trusts cryptographically-verified
Cloudflare Access identities instead of the built-in login.

- New internal/cfaccess package verifies the Cf-Access-Jwt-Assertion JWT
  against the team's JWKS (RS256, aud/iss/exp), returning the email claim
- middleware.RequireAuth gains a CF branch: verify JWT, match the email to
  an existing user (no auto-provisioning; 403 on unknown), inject the user
  per-request (stateless, no session cookie)
- Adds User.Email (additive column, app-layer uniqueness) plus
  GetUserByEmail/UpdateUserEmail helpers
- When enabled, built-in login/passkey/setup return 403; new public
  /api/v1/auth/config tells the SPA to show a Cloudflare notice and use the
  CF logout URL; admin Users UI gains an Email field/column
- CLI --create-admin gains --email to bootstrap the first admin
- Config: CLAWORC_CF_ACCESS_ENABLED / _TEAM_DOMAIN / _AUD with fail-fast
  validation; docs in docs/auth.md, CLAUDE.md, website_docs, helm values

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPx1Pv2gshfsJ5GyZqtbsY
CI's model-change guard requires a new migration whenever models change,
even for additive columns. Adds version 7 to add the users.email column
and its index, guarded by HasColumn/HasIndex so it is a no-op on fresh
installs where AutoMigrate already created it (mirrors 00005_add_team_ids).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPx1Pv2gshfsJ5GyZqtbsY
Add Cloudflare Access (Zero Trust) authentication support
Resolves merge conflicts for PR gluk-w#178 (jeremyhart:main -> gluk-w:main):
- CLAUDE.md: keep both CF Access env vars and upstream's new vars + Terminology section
- config.go: merge strings/time imports and CFAccess* + AllowedHostMounts settings
- UserModal.tsx: adopt upstream @common/ alias paths while keeping useAuth/email handling
- CloudflareLoginNotice.tsx: relocate under upstream's pages/ -> app/pages/ rename
- website_docs/*.mdx: accept upstream removal of website_docs/
- Renumber migration_00007_add_user_email -> 00009 to avoid version collision
  with upstream's 00007 (backfill_instance_uuid) and 00008

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1hLK8p2QkG8j5s4CPfHFL
Add a first-class "Cloudflare AI Gateway" option to the Add Provider form so
users can enter their Account ID and Gateway Name directly instead of hand-
crafting a base URL. These are path segments in the Cloudflare endpoint
(.../v1/{account}/{gateway}/compat), not separate credentials, so the UI now
collects them and assembles the universal (/compat) endpoint URL.

Backend:
- New api_type `cloudflare-ai-gateway`. Because the base ends in `/compat`
  rather than `/v1`, the standard /v1 dedup does not fire, so its RewritePath
  strips the client's leading /v1 to yield `.../compat/chat/completions`. It
  speaks OpenAI-completions format and is declared to OpenClaw as
  `openai-completions`.
- Support Authenticated gateways: a new encrypted `cf_ai_gateway_token` column
  on LLMProvider is forwarded as `cf-aig-authorization: Bearer <token>` in
  addition to the upstream provider's Bearer key.

Frontend:
- ProviderModal collects Account ID, Gateway Name, optional Gateway Token, the
  upstream provider API key, and models (provider/model IDs). Parses the fields
  back out of the base URL when editing.

Docs: document the new api_type in docs/virtual-keys.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KynRXwNky8tzc8yYEomRG7
The Migration Drift Check requires a companion migration whenever
models.go changes. Add migration 00010 to add the additive, encrypted
cf_ai_gateway_token column on llm_providers, mirroring 00009. Guarded by
HasColumn so it is a no-op on fresh installs where AutoMigrate already
created the column.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KynRXwNky8tzc8yYEomRG7
…nfig-skqr2h

Add Cloudflare AI Gateway provider support
Follow-up to #5, addressing review feedback. The /compat connector now uses
Cloudflare's Unified Billing model, which collapses the auth design to the
standard single-credential flow.

Connector / UI:
- Rename the option to "Cloudflare AI Gateway - Unified Billing" and sort it
  alphabetically in the provider dropdown (was pinned at the bottom).
- Replace the confusing "Provider API Key" + separate "Gateway Token" fields
  with one "Cloudflare API Token" (a token with AI Gateway Run permission).
  Cloudflare authenticates and bills the upstream provider, so no per-provider
  keys are needed.
- Restore the Test button (it now works via the standard probe + token).

Backend:
- cloudflare-ai-gateway now embeds openAICompletions: the Cloudflare token is
  sent as Authorization: Bearer (stored as the provider's api_key), identical
  to other providers. Only the path differs (strip leading /v1 for /compat).
- Drop the cf_ai_gateway_token column and all cf-aig-authorization plumbing.

Migrations / CI:
- Delete migration 00010. Adding a column is additive and handled by
  AutoMigrate — docs/migrations.md explicitly says not to write a migration
  for it. The CI "Guard against model changes without a new migration" step
  contradicted that policy and forced the noise migration, so remove it;
  migrationcheck still verifies AutoMigrate + migrations cover every model.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KynRXwNky8tzc8yYEomRG7
…xups

Simplify Cloudflare AI Gateway to Unified Billing; fix migration guard
echo " Run 'make migration' from control-plane/ to author one."
exit 1
fi

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jeremyhart why did you decide to remove this step?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, that wasn't meant to make it through to the PR

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you restore the file content?

export interface User {
id: number;
username: string;
email?: string;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to put email in the "username" for cloudflare accounts?

@jeremyhart jeremyhart Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be too ambiguous to the user, it will be the email matching from the Cloudflare side so you would have to enfore all usernames in the database are emails.

@gluk-w gluk-w left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff! I'm eager to merge this to the mainstream. Please address the comments, try to reuse "username" for CF emails if possible

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants