SupaOAuth is a SupaCloud-hosted enterprise IAM and user-center surface comparable to Logto. In the default runtime_mode=gotrue, it enhances Supabase Auth instead of replacing it: GoTrue keeps the OAuth/OIDC protocol runtime, JWT signing, refresh tokens, MFA, sessions, and auth.users, while SupaOAuth provides hosted UI, Admin Console, organization/RBAC governance, audit, configuration, and compatibility tooling.
It is not a thin admin panel for GoTrue environment variables.
The default product contract is upstream-compatible enhancement: a SupaOAuth
installation must run against the stock upstream GoTrue/Supabase Auth runtime
managed by SupaCloud. It may use documented GoTrue/Supabase extension points,
SupaCloud Management API, SupaCloud Functions/Pages, and official Supabase SDKs,
but must not require a SupaOAuth-patched GoTrue binary, custom /auth/v1/*
semantics, or forked @supabase/supabase-js / Auth UI packages.
Three explicit layers:
- Supabase-compatible runtime — GoTrue, Kong, and Supabase API paths handle OIDC/OAuth protocol, JWT signing,
auth.users, and RLS-compatible claims. - Logto-like product surface — SupaOAuth provides the Admin Console, hosted pages, SDKs, and overlay APIs while calling SupaCloud Management API for Applications, Users, Organizations, RBAC, Audit, Webhooks, and Providers.
- SupaCloud orchestration — Owns the Management API source of truth and applies GoTrue env injection, Kong routes, instance lifecycle, user/MFA proxy, webhook delivery, and managed background jobs.
SupaOAuth does not reimplement OIDC token signing or authorization-code issuance. GoTrue handles the protocol runtime. SupaOAuth is the SupaCloud Function BFF, product overlay owner, SDK/API facade, and runtime verifier.
See docs/architecture.md for full details.
Compatibility is a hard requirement. SupaOAuth must not break:
supabase-jsauth flowsauth.usersas primary identity (gotrue mode)- JWT claims needed by RLS and Supabase Auth hooks (
iss,aud,exp,iat,sub,role,aal,session_id,email,phone,is_anonymous) plus preserved metadata claims (app_metadata,user_metadata) - OIDC discovery and JWKS endpoints
- Supabase API paths (
/auth/v1/*,/rest/v1/*,/storage/v1/*,/realtime/v1/*) - Self-hosted deployment via SupaCloud
See docs/supabase-compatibility.md for the full spec.
packages/
authorization-core/ # Dependency-free effective-grant resolution and in-memory decisions
authorization-postgres/ # Reviewable application-owned PostgreSQL/RLS SQL generators
authorization-conformance/ # Pure adapter, SQL, and EXPLAIN checks
auth-server/ # Elysia/Bun Management API + BFF + SupaCloud adapter + metadata APIs
admin-console/ # SvelteKit + @svadmin/core management UI
shared/ # Shared schemas and types
sdks/typescript/ # Management API client SDK
sdks/auth-ui/ # Supabase Auth UI adapter (React/Svelte)
packages/admin-console is the only Admin Console source of truth for development, testing, builds, and deployment. Root src/ is non-executable historical code: it is not a mirror and is never a release input. Use bun run dev:admin or the filtered package scripts; the root Vite entry fails closed to prevent stale UI builds.
Install dependencies and create .env from .env.example:
bun run setupEdit .env with your SupaCloud values, then install into the target SupaCloud project. SupaCloud Management API applies the hosted migrations:
bun run build
bun run install:supacloudProduction installation requires explicit ADMIN_SSO_ISSUER and
ADMIN_SSO_CLIENT_ID. The installer verifies that the DB-first Admin allowlist
has at least one email/domain entry, or that an explicit server-only
ADMIN_SSO_ALLOWED_EMAILS / ADMIN_SSO_ALLOWED_DOMAINS fallback is present.
Allowlist values are never printed. The Admin SPA is published under
admin-console/build/** in the same multi-file Function bundle and continues
to call the BFF through same-origin /api.
Start the local development function shim and admin console together:
bun run devThe admin console runs on http://localhost:5173/admin. During development, Vite proxies /api/* to the local SupaCloud Function emulator on http://localhost:4010/*, so browser code still uses VITE_AUTH_SERVER_URL=/api. All SupAuth HTTP execution must use packages/auth-server/src/supacloud-function.ts; there is no standalone auth-server service.
Useful commands:
bun run dev:function # local SupaCloud Function emulator only
bun run dev:admin # admin-console only
bun run build # SupaCloud app manifest + Function + Admin static artifacts
bun run check # typecheck + tests + admin-console buildRelease-managed npm packages:
# Management API client
npm install @supauth/sdk-typescript
# Supabase Auth UI adapter (React/Svelte)
npm install @supauth/sdk-auth-ui
# Shared types and claims mapping
npm install @supauth/shared
# Application API authorization decisions
npm install @supauth/authorization-core
# PostgreSQL/RLS generation and conformance gates
npm install --save-dev @supauth/authorization-postgres @supauth/authorization-conformanceThe authorization packages are public on npm and form a business data-plane kit, not a second SupAuth or SupaCloud RBAC source of truth. A native SupaCloud/GoTrue application can use the kit without installing SupAuth: verified iss + sub identify the principal, one authorization schema fixes the application boundary, and the application projects current exact allow grants from its own policy tables. SupAuth/OAuth tokens use the same path with an additional signed application-claim consistency check. Ordinary users need no extra runtime service or JWT-mutating package. See docs/application-authorization-kit.md.
Management API usage:
import { SupaOAuthClient } from "@supauth/sdk-typescript";
const client = new SupaOAuthClient({
baseUrl: "https://auth.your-domain.com",
accessToken: "<admin-token>",
});
// Applications
const app = await client.createApplication({
name: "My Web App",
type: "web",
redirect_uris: ["https://your-app.com/callback"],
});
// Resolve per-app sign-in experience
const experience = await client.resolvePublicSignInExperience({
application_id: app.client_id,
});
// i18n phrases
const phrases = await client.getPublicPhrases("zh-CN");Auth UI bridge (React example):
import { resolveSupabaseAuthUiConfig } from "@supauth/sdk-auth-ui";
import { Auth } from "@supabase/auth-ui-react";
const config = await resolveSupabaseAuthUiConfig({
baseUrl: "https://auth.your-domain.com",
applicationId: "your-app-client-id",
locale: "zh-CN",
});
<Auth supabaseClient={supabase} {...config.auth} />Auth server (server-side only, no VITE_ prefix):
PORT=4010
HOST=0.0.0.0
SUPACLOUD_INTERNAL_API_URL=<injected-by-supacloud>
SUPACLOUD_INTERNAL_TOKEN=<injected-by-supacloud-never-expose-to-browser>
# Independent 32+ character secret; the Management API and Function must use the same value.
# Never expose it to the browser or reuse SUPACLOUD_INTERNAL_TOKEN.
SUPAOAUTH_BFF_SIGNING_SECRET=<injected-by-supacloud-secret>
SUPACLOUD_PROJECT_REF=<injected-by-supacloud>
SUPACLOUD_RUNTIME_URL=<injected-by-supacloud>
SUPAUTH_PUBLIC_URL=https://auth.your-domain.com
TRUST_PROXY_HEADERS=0
RUNTIME_MODE=gotrue
SUPACLOUD_DATABASE_URL=<injected-by-supacloud>
CORS_ORIGINS=http://localhost:5173
ADMIN_TOKEN=<development-admin-token>
ADMIN_SSO_ISSUER=https://auth.your-domain.com/auth/v1
ADMIN_SSO_CLIENT_ID=<registered-admin-oauth-client-id>
# Optional server-only fallback when the DB allowlist is empty.
ADMIN_SSO_ALLOWED_EMAILS=
ADMIN_SSO_ALLOWED_DOMAINS=
LOG_LEVEL=infoOptional P0-9 live OAuth 2.1 compatibility fixture:
RUN_SUPABASE_RUNTIME_COMPAT=0
RUN_SUPABASE_OAUTH21_COMPAT=0
OAUTH21_CLIENT_ID=<registered-oauth-client-id>
OAUTH21_REDIRECT_URI=http://localhost:3000/oauth/callback
OAUTH21_ACCESS_TOKEN=<optional-oauth-access-token>
OAUTH21_REFRESH_TOKEN=<optional-oauth-refresh-token>
OAUTH21_TOKEN_AUTH_METHOD=none
OAUTH21_CLIENT_SECRET=<optional-client-secret>Admin console (browser-side):
VITE_AUTH_SERVER_URL=/apiNo management tokens or service-role keys in VITE_* variables.
Optional:
SUPACLOUD_STORAGE_URL=https://project-ref.supacloud.example.com
AUTH_SERVER_PROXY_TARGET=http://localhost:4010SupaOAuth 是一个部署在 SupaCloud 上的企业 IAM 和用户中心产品表面,定位接近 Logto。默认 runtime_mode=gotrue 下,它增强 Supabase Auth,而不是替换 Supabase Auth:OAuth/OIDC 协议 runtime、JWT 签名、refresh token、MFA、会话和 auth.users 仍由 GoTrue 负责;SupaOAuth 提供 hosted UI、Admin Console、组织/RBAC 治理、审计、配置和兼容性工具。
它不是 GoTrue 环境变量的轻量管理面板。
默认产品契约是“基于上游 Supabase Auth 增强”:SupaOAuth 安装后必须能运行在
SupaCloud 管理的未 fork 上游 GoTrue/Supabase Auth runtime 之上。它可以使用
GoTrue/Supabase 已文档化的扩展点、SupaCloud Management API、SupaCloud
Functions/Pages 和官方 Supabase SDK,但不能要求 SupaOAuth patched GoTrue
二进制、自定义 /auth/v1/* 语义,或 fork 版 @supabase/supabase-js / Auth UI
包。
系统分为三层:
- Supabase 兼容 runtime — GoTrue、Kong 和 Supabase API 路径负责 OIDC/OAuth 协议、JWT 签名、
auth.users和 RLS 兼容 claims。 - 类似 Logto 的产品表面 — SupaOAuth 提供 Admin Console、hosted pages、SDK 和 overlay API;Applications、Users、Organizations、RBAC、Audit、Webhooks、Providers 等主数据通过 SupaCloud Management API 管理。
- SupaCloud 编排层 — 拥有 Management API source of truth,并负责 GoTrue env 注入、Kong 路由、实例生命周期、用户/MFA 代理、Webhook 投递和托管后台任务。
SupaOAuth 不重新实现 OIDC token 签名或 authorization code 签发。协议 runtime 由 GoTrue 负责;SupaOAuth 是 SupaCloud Function BFF、产品 overlay owner、SDK/API facade 和 runtime verifier。
完整说明见 docs/architecture.md。
兼容性是硬性要求。SupaOAuth 不能破坏:
supabase-jsauth flowsauth.users作为主身份表(gotrue mode)- RLS 和 Supabase Auth hooks 所需的 JWT claims(
iss、aud、exp、iat、sub、role、aal、session_id、email、phone、is_anonymous)以及需要保留的 metadata claims(app_metadata、user_metadata) - OIDC discovery 和 JWKS endpoints
- Supabase API 路径(
/auth/v1/*、/rest/v1/*、/storage/v1/*、/realtime/v1/*) - 通过 SupaCloud 支持 self-hosted 部署
完整规范见 docs/supabase-compatibility.md。
packages/
authorization-core/ # 零依赖有效授权解析与内存权限判定
authorization-postgres/ # 可审查的业务本地 PostgreSQL/RLS SQL 生成器
authorization-conformance/ # 纯函数 adapter、SQL 与 EXPLAIN 检查器
auth-server/ # Elysia/Bun Management API + BFF + SupaCloud adapter + metadata APIs
admin-console/ # SvelteKit + @svadmin/core management UI
shared/ # Shared schemas and types
sdks/typescript/ # Management API client SDK
sdks/auth-ui/ # Supabase Auth UI adapter (React/Svelte)
packages/admin-console 是 Admin Console 开发、测试、构建和部署的唯一 source of truth。根目录 src/ 仅保留不可执行的历史代码:它不是同步镜像,也不会作为发布输入。请使用 bun run dev:admin 或 package filter 命令;根目录 Vite 入口会直接失败,防止误构建陈旧 UI。
安装依赖,并从 .env.example 创建 .env:
bun run setup填写 .env 中的 SupaCloud 配置,然后安装到目标 SupaCloud 项目。迁移由 SupaCloud Management API 托管执行:
bun run build
bun run install:supacloud生产安装必须显式提供 ADMIN_SSO_ISSUER 和 ADMIN_SSO_CLIENT_ID。安装器会
验证 DB 优先的管理员 email/domain allowlist 至少有一项,或者存在显式的服务端
ADMIN_SSO_ALLOWED_EMAILS / ADMIN_SSO_ALLOWED_DOMAINS 回退;allowlist 值不会
输出。Admin SPA 会随同一个多文件 Function bundle 发布到
admin-console/build/**,浏览器继续通过同源 /api 调用 BFF。
同时启动本地开发 function shim 和 admin console:
bun run devAdmin console 运行在 http://localhost:5173/admin。开发时 Vite 会把 /api/* 代理到本地 SupaCloud Function emulator http://localhost:4010/*,所以浏览器代码仍使用 VITE_AUTH_SERVER_URL=/api。所有 SupAuth HTTP 执行都必须使用 packages/auth-server/src/supacloud-function.ts,不再存在独立 auth-server service。
常用命令:
bun run dev:function # 只启动本地 SupaCloud Function emulator
bun run dev:admin # 只启动 admin-console
bun run build # 构建 SupaCloud app manifest + Function + Admin 静态产物
bun run check # typecheck + tests + admin-console build由 release 管理的 npm 包:
# Management API 客户端
npm install @supauth/sdk-typescript
# Supabase Auth UI 适配层(React/Svelte)
npm install @supauth/sdk-auth-ui
# 共享类型和 Claims 映射
npm install @supauth/shared
# 应用 API 的授权判定
npm install @supauth/authorization-core
# PostgreSQL/RLS 生成与一致性门禁
npm install --save-dev @supauth/authorization-postgres @supauth/authorization-conformance三个 authorization 包已经公开发布到 npm,只统一授权协议、SQL 模板和一致性检查;业务 membership、role assignment、权限事实与审计仍由各子系统本地持有,不集中到 SupAuth 或 SupaCloud。原生 SupaCloud/GoTrue 应用无需安装 SupAuth:签名 JWT 的 iss + sub 标识主体,每个授权 schema 固定应用边界,应用从自己的策略表投影当前精确 allow grant;SupAuth/OAuth token 在同一路径上额外执行签名 application claim 一致性检查。普通用户不需要额外 runtime 服务或修改 JWT 的包。完整边界与发布机制见 docs/application-authorization-kit.md。
Management API 用法:
import { SupaOAuthClient } from "@supauth/sdk-typescript";
const client = new SupaOAuthClient({
baseUrl: "https://auth.your-domain.com",
accessToken: "<admin-token>",
});
// 应用管理
const app = await client.createApplication({
name: "My Web App",
type: "web",
redirect_uris: ["https://your-app.com/callback"],
});
// 按应用解析登录体验
const experience = await client.resolvePublicSignInExperience({
application_id: app.client_id,
});
// 国际化短语
const phrases = await client.getPublicPhrases("zh-CN");Auth UI 桥接(React 示例):
import { resolveSupabaseAuthUiConfig } from "@supauth/sdk-auth-ui";
import { Auth } from "@supabase/auth-ui-react";
const config = await resolveSupabaseAuthUiConfig({
baseUrl: "https://auth.your-domain.com",
applicationId: "your-app-client-id",
locale: "zh-CN",
});
<Auth supabaseClient={supabase} {...config.auth} />
### 环境变量
Auth server(仅服务端使用,不加 `VITE_` 前缀):
```sh
PORT=4010
HOST=0.0.0.0
SUPACLOUD_INTERNAL_API_URL=<由-supacloud-注入>
SUPACLOUD_INTERNAL_TOKEN=<由-supacloud-注入-绝不暴露到浏览器>
# 与 SupaCloud Management API 共用的独立 32+ 字符 secret;只在服务端保存。
# 不得复用 SUPACLOUD_INTERNAL_TOKEN 或加密密钥,也不得暴露给浏览器。
SUPAOAUTH_BFF_SIGNING_SECRET=<由-supacloud-注入-secret>
SUPACLOUD_PROJECT_REF=<由-supacloud-注入>
SUPACLOUD_RUNTIME_URL=<由-supacloud-注入>
SUPAUTH_PUBLIC_URL=https://auth.your-domain.com
TRUST_PROXY_HEADERS=0
RUNTIME_MODE=gotrue
SUPACLOUD_DATABASE_URL=<由-supacloud-注入>
CORS_ORIGINS=http://localhost:5173
ADMIN_TOKEN=<development-admin-token>
ADMIN_SSO_ISSUER=https://auth.your-domain.com/auth/v1
ADMIN_SSO_CLIENT_ID=<已注册的-admin-oauth-client-id>
# 仅当 DB allowlist 为空时使用的可选服务端回退。
ADMIN_SSO_ALLOWED_EMAILS=
ADMIN_SSO_ALLOWED_DOMAINS=
LOG_LEVEL=info
可选的 P0-9 live OAuth 2.1 兼容性测试配置:
RUN_SUPABASE_RUNTIME_COMPAT=0
RUN_SUPABASE_OAUTH21_COMPAT=0
OAUTH21_CLIENT_ID=<registered-oauth-client-id>
OAUTH21_REDIRECT_URI=http://localhost:3000/oauth/callback
OAUTH21_ACCESS_TOKEN=<optional-oauth-access-token>
OAUTH21_REFRESH_TOKEN=<optional-oauth-refresh-token>
OAUTH21_TOKEN_AUTH_METHOD=none
OAUTH21_CLIENT_SECRET=<optional-client-secret>Admin console(浏览器侧):
VITE_AUTH_SERVER_URL=/api不要把 management token 或 service-role key 放进任何 VITE_* 变量。
可选项:
SUPACLOUD_STORAGE_URL=https://project-ref.supacloud.example.com
AUTH_SERVER_PROXY_TARGET=http://localhost:4010