基于Supabase实现企业SSO接入
云原生数据仓库AnalyticDB PostgreSQL版深度集成开源Supabase身份认证系统,支持企业通过配置SAML 2.0单点登录(SSO)接入,满足企业场景的使用需求,尤其适合构建企业私域的Coding Agent平台。本文介绍如何为Supabase项目配置SAML SSO,实现企业用户的统一身份认证。
前提条件
已创建Supabase项目。创建时间需晚于以下日期:
中国地域:2026年04月01日。
新加坡:2025年04月01日。
如果您的SSO身份提供商(IdP)和Supabase不在同一个VPC,需为Supabase开通公网访问。
操作步骤
步骤一:获取Supabase项目信息
开通Supabase项目后,可获取以下Supabase信息项。
信息项 | 说明 | 示例 |
Supabase URL | Supabase项目的访问地址。 |
|
Anon Key(公钥) | 可在前端代码中使用的公开密钥。 |
|
Service Role Key(密钥) | 仅限后端使用的密钥,用于管理操作,切勿泄露到前端代码中。 |
|
验证项目状态
curl 'https://<your-supabase-url>/auth/v1/health' \
-H 'apikey: <your-anon-key>'返回示例
{"version":"vunspecified","name":"GoTrue","description":"GoTrue is a user registration and authentication API"}步骤二:获取SP元数据
SAML服务提供商(SP)的元数据可通过以下地址公开访问:
https://<your-supabase-url>/sso/saml/metadata您需要用到以下两个关键地址,在后续步骤中将其配置到IdP中:
地址类型 | 值 |
ACS URL |
|
Entity ID |
|
步骤三:配置重定向URL
登录Supabase Dashboard,在Dashboard侧边栏单击Authentication>URL Configuration,进入Site URL配置页面。
在Site URL区域,填入前端域名地址,然后单击Save changes保存。
步骤四:注册身份提供商(IdP)
使用Management API将您的IdP注册到Supabase。
方式一:通过Metadata URL注册(推荐)
如果您的IdP提供公开的Metadata URL(大多数IdP都支持),运行以下命令:
curl -X POST 'https://<your-supabase-url>/auth/v1/admin/sso/providers' \
-H 'apikey: <your-service-role-key>' \
-H 'Authorization: Bearer <your-service-role-key>' \
-H 'Content-Type: application/json' \
-d '{
"type": "saml",
"metadata_url": "https://<your-idp>/samlp/metadata/<client-id>",
"domains": ["yourdomain.com"]
}'方式二:通过Metadata XML注册
如果Metadata URL不可公开访问,可直接提供XML内容:
# 1. 在可访问IdP的环境中获取Metadata XML
curl 'https://<your-idp>/samlp/metadata/<client-id>' > idp-metadata.xml
# 2. 注册到Supabase
curl -X POST 'https://<your-supabase-url>/auth/v1/admin/sso/providers' \
-H 'apikey: <your-service-role-key>' \
-H 'Authorization: Bearer <your-service-role-key>' \
-H 'Content-Type: application/json' \
-d '{
"type": "saml",
"metadata_xml": "<EntityDescriptor ...>...</EntityDescriptor>",
"domains": ["yourdomain.com"]
}'Example:Auth0注册示例
Auth0的Metadata URL格式为:
https://<tenant>.auth0.com/samlp/metadata/<client-id>完整注册命令:
curl -X POST 'https://<your-supabase-url>/auth/v1/admin/sso/providers' \
-H 'apikey: <your-service-role-key>' \
-H 'Authorization: Bearer <your-service-role-key>' \
-H 'Content-Type: application/json' \
-d '{
"type": "saml",
"metadata_url": "https://dev-xxx.auth0.com/samlp/metadata/YOUR_CLIENT_ID",
"domains": ["example.com"]
}'保存返回的Provider ID
请保存返回结果中的id字段,前端发起登录时需要用到。
注册成功后返回示例:
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"type": "saml",
"saml": {
"entity_id": "urn:dev-xxx.auth0.com"
},
"domains": [{"domain": "example.com"}]
}步骤五:在IdP侧配置SP信息
注册完成后,需要在您的IdP中配置Supabase的SP信息。
需要填写的配置项
配置项 | 值 |
ACS URL(回调地址) |
|
Entity ID(SP实体ID) |
|
NameID Format |
|
前端回调URL |
|
以Auth0为例的配置步骤
进入Applications,单击目标应用。
单击Addons页签,启用SAML2 Web App。
在弹出窗口中完成以下配置:
Application Callback URL
Settings
单击Enable,然后单击Save。
在应用主页的Allowed Callback URLs中,添加您的前端地址。
步骤六:前端集成
安装依赖。
npm install @supabase/supabase-js初始化客户端。SAML SSO必须使用
implicitflow。import { createClient } from '@supabase/supabase-js' const supabase = createClient( 'https://<your-supabase-url>', '<your-anon-key>', { auth: { flowType: 'implicit', detectSessionInUrl: true, persistSession: true, autoRefreshToken: true, } } )发起SSO登录。
通过Provider ID发起登录:
const { data, error } = await supabase.auth.signInWithSSO({ providerId: '<your-provider-id>', options: { redirectTo: window.location.origin } }) if (error) { console.error('登录失败:', error.message) return } if (data?.url) { window.location.href = data.url }通过域名自动匹配Provider:
const { data, error } = await supabase.auth.signInWithSSO({ domain: 'yourdomain.com' })
处理登录回调。
IdP认证完成后会重定向回前端,URL格式为:
https://<your-app-domain>/#access_token=<jwt>&refresh_token=<token>&expires_in=3600&token_type=bearer由于supabase-js的初始化是异步的,建议手动解析URL hash:
function decodeJWT(token) { try { return JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))) } catch { return null } } async function init() { const hash = window.location.hash.substring(1) if (hash.includes('access_token')) { const params = new URLSearchParams(hash) const accessToken = params.get('access_token') const refreshToken = params.get('refresh_token') const expiresAt = params.get('expires_at') window.history.replaceState({}, '', window.location.pathname) const payload = decodeJWT(accessToken) if (payload) { const session = { access_token: accessToken, refresh_token: refreshToken || '', expires_at: expiresAt ? parseInt(expiresAt) : payload.exp, token_type: 'bearer', user: { id: payload.sub, email: payload.email, role: payload.role, app_metadata: payload.app_metadata || {}, user_metadata: payload.user_metadata || {}, } } onLoginSuccess(session) return } } const { data: { session } } = await supabase.auth.getSession() if (session) { onLoginSuccess(session) } else { showLoginPage() } } document.addEventListener('DOMContentLoaded', init)查看JWT Payload结构。
登录成功后,JWT解码后包含以下关键字段:
{ "sub": "8c308927-4ba1-4507-b6f8-f751b791****", "email": "user@example.com", "role": "authenticated", "aal": "aal1", "amr": [{ "method": "sso/saml", "timestamp": 1774928127, "provider": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }], "app_metadata": { "provider": "sso:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }, "session_id": "aeaf9cdb-dbcd-46ab-95a5-ff956e69****" }通过
amr[0].method === 'sso/saml'可确认用户通过SSO登录。
完整示例项目
以下是一个可直接运行的最小化示例。在线Demo,请参见sso-demo。
main.js
import { createClient } from '@supabase/supabase-js' const SUPABASE_URL = 'https://<your-supabase-url>' const SUPABASE_ANON_KEY = '<your-anon-key>' const AUTH0_PROVIDER_ID = '<auth0-provider-id>' const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { auth: { flowType: 'implicit', detectSessionInUrl: true } }) function decodeJWT(token) { try { return JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))) } catch { return null } } window.signInWithAuth0 = async () => { const { data, error } = await supabase.auth.signInWithSSO({ providerId: AUTH0_PROVIDER_ID, options: { redirectTo: window.location.origin } }) if (error) return alert(error.message) if (data?.url) window.location.href = data.url } window.signOut = async () => { await supabase.auth.signOut() window.location.reload() } async function init() { const hash = window.location.hash.substring(1) if (hash.includes('access_token')) { const p = new URLSearchParams(hash) const token = p.get('access_token') const payload = decodeJWT(token) window.history.replaceState({}, '', window.location.pathname) if (payload) { showUser({ token, payload, refresh_token: p.get('refresh_token') }) return } } const { data: { session } } = await supabase.auth.getSession() if (session) { showUser({ token: session.access_token, payload: decodeJWT(session.access_token) }) } } function showUser({ token, payload }) { document.getElementById('login').style.display = 'none' document.getElementById('user').innerHTML = ` <p><strong>邮箱:</strong>${payload.email}</p> <p><strong>用户 ID:</strong>${payload.sub}</p> <p><strong>认证方式:</strong>${payload.amr?.[0]?.method}</p> <button onclick="signOut()">退出登录</button> ` } document.addEventListener('DOMContentLoaded', init)index.html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SSO Login</title> </head> <body> <div id="login"> <button onclick="signInWithAuth0()">使用 Auth0 登录</button> </div> <div id="user"></div> <script type="module" src="/main.js"></script> </body> </html>
基于上述代码可以实现Auth0的SSO登录。
|
|
您可以在Supabase的auth.users表中查看SSO用户记录,其is_sso_user字段标记为TRUE。

管理Provider
您可以通过下面的接口新增或者删除SSO Provider,以下操作均需使用Service Role Key。
查询Provider列表
curl 'https://<your-supabase-url>/auth/v1/admin/sso/providers' \
-H 'apikey: <your-service-role-key>' \
-H 'Authorization: Bearer <your-service-role-key>'您也可以通过Supabase Dashboard查看auth.sso_domains和auth.sso_providers表,确认已注册的SSO Provider。

查询单个Provider
curl 'https://<your-supabase-url>/auth/v1/admin/sso/providers/<provider-id>' \
-H 'apikey: <your-service-role-key>' \
-H 'Authorization: Bearer <your-service-role-key>'更新Provider
curl -X PUT 'https://<your-supabase-url>/auth/v1/admin/sso/providers/<provider-id>' \
-H 'apikey: <your-service-role-key>' \
-H 'Authorization: Bearer <your-service-role-key>' \
-H 'Content-Type: application/json' \
-d '{"metadata_url": "https://<your-idp>/samlp/metadata/<client-id>"}'删除Provider
curl -X DELETE 'https://<your-supabase-url>/auth/v1/admin/sso/providers/<provider-id>' \
-H 'apikey: <your-service-role-key>' \
-H 'Authorization: Bearer <your-service-role-key>'常见问题
单击登录按钮后没有跳转
确认createClient中设置了flowType: 'implicit'。默认的PKCE flow在SAML回调场景下可能导致问题。
回调后仍显示登录页
不要仅依赖supabase.auth.getSession()处理回调。SDK的初始化是异步的,请在DOMContentLoaded时手动解析URL hash中的access_token。
登录失败:No such SSO provider
检查前端代码中的
providerId是否与注册时返回的ID一致。强制刷新浏览器(Cmd+Shift+R / Ctrl+Shift+R)清除缓存。
登录失败:Failed to fetch
检查网络是否能访问Supabase项目。
在浏览器控制台执行测试命令验证连通性。
登录后跳回错误的地址
在signInWithSSO()的options.redirectTo中明确指定前端地址,该参数会覆盖实例默认的Site URL。
SAML RelayState has expired
SSO登录URL有时效性(通常几分钟)。请勿复用旧的登录URL,每次单击登录按钮时应重新调用signInWithSSO()。
Auth0报错:invalid_request
检查Auth0的Allowed Callback URLs是否包含您的前端域名,Application Callback URL是否为Supabase的ACS URL。

