Skip to content
Self-Hosting

配置 SAML 单点登录

Set up SAML 2.0 Single Sign-On for self-hosted Supabase with Docker.

SAML 2.0 单点登录(SSO)让你的用户可以通过企业身份提供商(IdP)进行认证,比如 Okta、Azure AD(Entra ID)、Google Workspace,或任何兼容 SAML 2.0 的提供商。和 OAuth 提供商不同,SAML IdP 不是通过环境变量配置的——它们是通过 Auth 管理 API 在运行时动态管理的。

🌐 SAML 2.0 SSO lets your users authenticate through an enterprise Identity Provider (IdP) such as Okta, Azure AD (Entra ID), Google Workspace, or any SAML 2.0-compliant provider. Unlike OAuth providers, SAML IdPs are not configured through environment variables - they are managed dynamically at runtime through the Auth admin API.

本指南涵盖了完整的设置:生成签名密钥、在你的 Supabase 实例中启用 SAML、注册身份提供商(IdP),以及将单点登录(SSO)集成到你的应用中。

🌐 This guide covers the full setup: generating a signing key, enabling SAML in your Supabase instance, registering an IdP, and integrating SSO into your application.

在你开始之前 #

🌐 Before you begin

你需要:

🌐 You need:

  • 一个正在运行的自托管 Supabase 实例(参见 设置指南
  • 已安装 Open SSL(用于生成密钥)
  • 你的 IdP 的 SAML 元数据 URL 或元数据 XML
  • .env 文件中的 SERVICE_ROLE_KEY(用于管理员 API 调用)
  • API_EXTERNAL_URL 设置为你的 Supabase Auth 服务的公开可访问 URL(例如,https://<your-domain>/auth/v1)。用于构建 SAML 服务提供者实体 ID 和 ACS 端点 URL 的基础

Supabase 中 SAML SSO 的工作原理 #

🌐 How SAML SSO works in Supabase

SAML 单点登录配置了两层:

🌐 SAML SSO is configured in two layers:

  1. 全局 SAML 启用(环境变量) - 一小组环境变量,用于启用 SAML 引擎并提供签名密钥。这些放在 .envdocker-compose.yml
  2. 每个身份提供商的配置(管理员 API) - 通过 Auth 管理 API,可以在运行时注册、更新和删除各个身份提供商。添加或移除提供商时无需重启。

登录流程如下:

🌐 The login flow works as follows:

  1. 你的应用会使用域名或 provider_id 调用 POST /auth/v1/sso
  2. Auth 生成一个 SAML AuthnRequest 并返回到 IdP 的重定向 URL
  3. 用户在身份提供者处进行身份验证
  4. 身份提供者会将 SAML 响应通过 POST 发送到 POST /auth/v1/sso/saml/acs
  5. Auth 验证声明,创建或关联用户,并发放会话
  6. 用户会被重定向回你的应用,并带上会话令牌

步骤1:生成一个RSA私钥 #

🌐 Step 1: Generate an RSA private key

SAML 请求必须被签名。GOTRUE_SAML_PRIVATE_KEY 期望的值是一个 Base64 编码的 PKCS#1 DER RSA 私钥(最少需要 2048 位密钥)。可以用以下方式生成:

🌐 SAML requests must be signed. The value expected by GOTRUE_SAML_PRIVATE_KEY is a Base64-encoded PKCS#1 DER RSA private key (with a 2048-bit key as the minimum requirement). Generate it with:

1
openssl genpkey -algorithm RSA -out pk_pkcs8.pem -quiet && \
2
openssl pkey -in pk_pkcs8.pem -out pk_rsa1.der -outform DER -traditional && \
3
base64 -w 0 -i pk_rsa1.der

上面的命令:

🌐 The commands above:

  1. 生成一个 PKCS#8 PEM 格式的 RSA 私钥
  2. 把密钥转换成 PKCS#1 DER 格式
  3. 将密钥进行 Base64 编码,以用作 GOTRUE_SAML_PRIVATE_KEY 的值

保存 Base64 输出——确保以单行复制,忽略末尾的换行。删除临时文件。

🌐 Save the Base64 output - make sure to copy it as single line, ignoring the trailing newline. Remove the temporary files.

步骤 2:添加环境变量 #

🌐 Step 2: Add environment variables

把以下内容添加到你的 .env 文件中:

🌐 Add the following to your .env file:

.env
1
############
2
# SAML SSO
3
4
############
5
6
SAML_ENABLED=true
7
SAML_PRIVATE_KEY=<your-base64-encoded-private-key>
8
9
# Optional: accept encrypted SAML assertions from IdPs (default: false)
10
# SAML_ALLOW_ENCRYPTED_ASSERTIONS=false
11
12
# Optional: how long relay state tokens remain valid (default: 2m0s)
13
# SAML_RELAY_STATE_VALIDITY_PERIOD=2m0s
14
15
# Optional: rate limit on the ACS endpoint (requests per second, default: 15)
16
# SAML_RATE_LIMIT_ASSERTION=15

步骤 3:将 SAML 变量传递给 Auth 容器 #

🌐 Step 3: Pass SAML variables to the Auth container

docker-compose.yml 中,将 SAML 环境变量添加到 auth 服务。Auth 期望所有的配置变量都有 GOTRUE_ 前缀:

🌐 In docker-compose.yml, add the SAML environment variables to the auth service. Auth expects the GOTRUE_ prefix for all of its configuration variables:

docker-compose.yml
1
auth:
2
environment:
3
# ... existing variables ...
4
5
# SAML SSO
6
GOTRUE_SAML_ENABLED: ${SAML_ENABLED}
7
GOTRUE_SAML_PRIVATE_KEY: ${SAML_PRIVATE_KEY}
8
# GOTRUE_SAML_ALLOW_ENCRYPTED_ASSERTIONS: ${SAML_ALLOW_ENCRYPTED_ASSERTIONS}
9
# GOTRUE_SAML_RELAY_STATE_VALIDITY_PERIOD: ${SAML_RELAY_STATE_VALIDITY_PERIOD}
10
# GOTRUE_SAML_RATE_LIMIT_ASSERTION: ${SAML_RATE_LIMIT_ASSERTION}

步骤4:重启容器 #

🌐 Step 4: Restart the containers

应用配置更改:

🌐 Apply the configuration changes:

1
sh run.sh recreate

确认认证服务是否正常:

🌐 Verify the Auth service is healthy:

1
sh run.sh status auth

步骤5:获取你的服务提供商元数据 #

🌐 Step 5: Retrieve your service provider metadata

一旦启用 SAML,你的 Supabase 实例会在 {API_EXTERNAL_URL}/sso/saml/metadata 暴露服务提供者(SP)元数据。

🌐 Once SAML is enabled, your Supabase instance exposes service provider (SP) metadata at {API_EXTERNAL_URL}/sso/saml/metadata.

curl 验证一下:

🌐 Verify it using curl:

1
curl http://<your-domain>/auth/v1/sso/saml/metadata

这会返回一个 XML 文档,里面包含你的 SP 实体 ID、ACS 端点 URL 和签名证书。你需要把这些提供给你的 IdP。

🌐 This returns an XML document containing your SP entity ID, ACS endpoint URL, and signing certificate. You will need to provide this to your IdP.

元数据中的关键值:

🌐 Key values in the metadata:

字段
实体ID{API_EXTERNAL_URL}/sso/saml/metadata
ACS URL{API_EXTERNAL_URL}/sso/saml/acs
NameID 格式persistent, emailAddress
签名证书来源于你的 SAML_PRIVATE_KEY

第6步:注册一个身份提供者 #

🌐 Step 6: Register an identity provider

使用 Auth 管理 API 注册你的 IdP。你需要 SERVICE_ROLE_KEY 来进行身份验证。

🌐 Use the Auth admin API to register your IdP. You need the SERVICE_ROLE_KEY for authentication.

🌐 Option A: Register with a metadata URL (recommended)

如果你的身份提供者(IdP)提供了元数据 URL,Auth 会自动获取并缓存这些元数据,并在它过期时刷新它:

🌐 If your IdP provides a metadata URL, Auth will fetch and cache the metadata automatically and refresh it when it becomes stale:

1
curl -X POST 'http://<your-domain>/auth/v1/admin/sso/providers' \
2
-H 'Authorization: Bearer your-service-role-key' \
3
-H 'Content-Type: application/json' \
4
-H 'apikey: your-service-role-key' \
5
-d '{
6
"type": "saml",
7
"metadata_url": "https://idp.example.com/saml/metadata",
8
"domains": ["example.com"],
9
"attribute_mapping": {
10
"keys": {
11
"email": {
12
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
13
},
14
"name": {
15
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
16
}
17
}
18
}
19
}'

选项B:使用内联元数据XML注册 #

🌐 Option B: Register with inline metadata XML

如果你有作为 XML 字符串的 IdP 元数据:

🌐 If you have the IdP metadata as an XML string:

1
curl -X POST 'http://<your-domain>/auth/v1/admin/sso/providers' \
2
-H 'Authorization: Bearer your-service-role-key' \
3
-H 'Content-Type: application/json' \
4
-H 'apikey: your-service-role-key' \
5
-d '{
6
"type": "saml",
7
"metadata_xml": "<EntityDescriptor ...>...</EntityDescriptor>",
8
"domains": ["example.com"]
9
}'

响应包括提供者 id(UUID)——保存它以便在你的应用中使用或以后管理:

🌐 The response includes the provider id (UUID) - save this for use in your application or for later management:

1
{
2
"id": "d3f5a1b2-...",
3
"resource_id": null,
4
"disabled": false,
5
"saml": {
6
"entity_id": "https://idp.example.com/saml",
7
"metadata_url": "https://idp.example.com/saml/metadata"
8
},
9
"domains": [{ "domain": "example.com" }],
10
"created_at": "...",
11
"updated_at": "..."
12
}

注册参数参考 #

🌐 Registration parameters reference

参数必填描述
type必须是 "saml"
metadata_url以下之一IdP 的 SAML 元数据的 HTTPS URL(自动刷新)
metadata_xml以下之一原始 IdP 元数据 XML 字符串
domains要关联的电子邮件域数组(例如,["acme.com"])。用于基于域的 SSO 查找。
attribute_mapping将 SAML 属性映射到用户声明(参见 属性映射
name_id_format请求特定的 NameID 格式:persistentemailAddresstransientunspecified
resource_id提供商的自定义外部标识符
disabled设置为 true 以注册但禁用提供商

第8步:配置你的身份提供者 #

🌐 Step 8: Configure your identity provider

在 IdP 端,创建一个新的 SAML 应用,并使用你的 SP 详细信息进行配置:

🌐 On the IdP side, create a new SAML application and configure it with your SP details:

IdP 设置
SP 实体 ID / 受众{API_EXTERNAL_URL}/sso/saml/metadata
ACS URL / 回复 URL{API_EXTERNAL_URL}/sso/saml/acs
NameID 格式persistent(推荐)或 emailAddress
签名证书从 SP 元数据 XML 上传或提供元数据 URL

特定身份提供商的配置 #

🌐 IdP-specific configuration

Okta 设置:

  • 创建一个“SAML 2.0”应用
  • 单点登录网址:{API_EXTERNAL_URL}/sso/saml/acs
  • 受众 URI(SP 实体 ID):{API_EXTERNAL_URL}/sso/saml/metadata
  • 默认中继状态:留空
  • 名称 ID 格式:Persistent

属性映射 #

🌐 Attribute mapping

属性映射让你可以控制 SAML 断言属性如何被转换为 Supabase 用户声明。如果没有提供映射,Auth 会使用合理的默认设置:

🌐 Attribute mapping lets you control how SAML assertion attributes are translated into Supabase user claims. If no mapping is provided, Auth uses sensible defaults:

默认电子邮件检测顺序:

  1. urn:oid:0.9.2342.19200300.100.1.3(LDAP 邮件 OID)
  2. http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
  3. http://schemas.xmlsoap.org/claims/EmailAddress
  4. 名为 mailMailemail 的属性
  5. 主题名称ID(如果看起来像电子邮件地址)

默认用户ID检测:

  1. urn:oasis:names:tc:SAML:attribute:subject-id 属性
  2. 主题 NameID(如果格式是 persistent

自定义属性映射示例 #

🌐 Custom attribute mapping example

将 IdP 特定属性映射到用户元数据:

🌐 Map IdP-specific attributes to user metadata:

1
{
2
"attribute_mapping": {
3
"keys": {
4
"email": {
5
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
6
},
7
"name": {
8
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
9
},
10
"department": {
11
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/department",
12
"default": "unknown"
13
},
14
"groups": {
15
"name": "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups",
16
"array": true
17
},
18
"role": {
19
"names": ["http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "role", "Role"],
20
"default": "member"
21
}
22
}
23
}
24
}

每个映射键支持:

🌐 Each mapping key supports:

字段描述
name要查找的主要 SAML 属性名称(与 NameFriendlyName 匹配,不区分大小写)
names按顺序尝试的备用属性名称数组
default如果断言中没有该属性,则使用的默认值
array设置为 true 以收集所有值(用于像组这样的多值属性)

映射的属性存储在用户的 raw_user_meta_data 中,并且可以通过你应用中的 user.user_metadata 使用。

🌐 Mapped attributes are stored in the user's raw_user_meta_data and are available via user.user_metadata in your application.

管理供应商 #

🌐 Managing providers

列出所有提供者 #

🌐 List all providers

1
curl 'http://<your-domain>/auth/v1/admin/sso/providers' \
2
-H 'Authorization: Bearer your-service-role-key' \
3
-H 'apikey: your-service-role-key'

按资源 ID 进行精确匹配筛选:

🌐 Filter by resource ID using exact match:

1
curl 'http://<your-domain>/auth/v1/admin/sso/providers?resource_id=my-idp' \
2
-H 'Authorization: Bearer your-service-role-key' \
3
-H 'apikey: your-service-role-key'

或前缀匹配:

🌐 or prefix match:

1
curl 'http://<your-domain>/auth/v1/admin/sso/providers?resource_id_prefix=prod-' \
2
-H 'Authorization: Bearer your-service-role-key' \
3
-H 'apikey: your-service-role-key'

获取特定的供应商 #

🌐 Get a specific provider

1
curl 'http://<your-domain>/auth/v1/admin/sso/providers/{provider_id}' \
2
-H 'Authorization: Bearer your-service-role-key' \
3
-H 'apikey: your-service-role-key'

更新供应商 #

🌐 Update a provider

1
curl -X PUT 'http://<your-domain>/auth/v1/admin/sso/providers/{provider_id}' \
2
-H 'Authorization: Bearer your-service-role-key' \
3
-H 'Content-Type: application/json' \
4
-H 'apikey: your-service-role-key' \
5
-d '{
6
"domains": ["example.com", "subsidiary.com"],
7
"attribute_mapping": {
8
"keys": {
9
"email": {
10
"name": "mail"
11
}
12
}
13
}
14
}'

禁用供应商(不删除) #

🌐 Disable a provider (without deleting)

1
curl -X PUT 'http://<your-domain>/auth/v1/admin/sso/providers/{provider_id}' \
2
-H 'Authorization: Bearer your-service-role-key' \
3
-H 'Content-Type: application/json' \
4
-H 'apikey: your-service-role-key' \
5
-d '{ "disabled": true }'

删除供应商 #

🌐 Delete a provider

1
curl -X DELETE 'http://<your-domain>/auth/v1/admin/sso/providers/{provider_id}' \
2
-H 'Authorization: Bearer your-service-role-key' \
3
-H 'apikey: your-service-role-key'

客户端集成 #

🌐 Client-side integration

使用 supabase-js #

🌐 Using supabase-js

1
import { createClient } from '@supabase/supabase-js'
2
3
const supabase = createClient('http://<your-domain>', 'your-anon-key')
4
5
// Option 1: SSO by email domain
6
const { data, error } = await supabase.auth.signInWithSSO({
7
domain: 'example.com',
8
})
9
10
// Option 2: SSO by provider ID
11
const { data, error } = await supabase.auth.signInWithSSO({
12
providerId: 'd3f5a1b2-...',
13
})
14
15
// Redirect the user to the IdP
16
if (data?.url) {
17
window.location.href = data.url
18
}

这两种方法都会返回一个带有 url 属性的对象——将用户重定向到这个 URL,开始在身份提供者处的认证。

🌐 Both methods return an object with a url property - redirect the user to this URL to begin authentication at the IdP.

直接使用 REST API #

🌐 Using the REST API directly

按字段:

🌐 By domain:

1
curl -X POST 'http://<your-domain>/auth/v1/sso' \
2
-H 'Content-Type: application/json' \
3
-H 'apikey: your-anon-key' \
4
-d '{
5
"domain": "example.com",
6
"skip_http_redirect": true
7
}'

按提供者ID:

🌐 By provider ID:

1
curl -X POST 'http://<your-domain>/auth/v1/sso' \
2
-H 'Content-Type: application/json' \
3
-H 'apikey: your-anon-key' \
4
-d '{
5
"provider_id": "d3f5a1b2-...",
6
"skip_http_redirect": true
7
}'

两者都返回 { "url": "https://idp.example.com/sso?SAMLRequest=..." }

🌐 Both return { "url": "https://idp.example.com/sso?SAMLRequest=..." }.

基于域名的查找 vs 基于提供商的查找 #

🌐 Domain-based vs provider-based lookup

方法使用场景
domain从用户的邮箱中提取域名,让 Auth 找到合适的身份提供商。最适合用户先输入邮箱的登录表单。
providerId当你确切知道提供商时使用——例如,一个专用的 “用 Okta 登录” 按钮。

测试登录流程 #

🌐 Test the login flow

  1. 打开你的应用并触发单点登录(或者使用上面的 curl 命令)
  2. 你应该会被重定向到你的身份提供者的登录页面
  3. 认证之后,IdP 会把信息回传到 ACS 端点
  4. Auth 会处理断言,然后带着会话令牌把你重定向回你的 SITE_URL(或 redirect_to URL)

要验证会话是否已创建:

🌐 To verify the session was created:

1
curl 'http://<your-domain>/auth/v1/user' \
2
-H 'Authorization: Bearer user-session-token' \
3
-H 'apikey: your-anon-key'

回复应该包括 app_metadata.provider: "sso:saml"user_metadata 中的任何映射属性。

🌐 The response should include app_metadata.provider: "sso:saml" and any mapped attributes in user_metadata.

环境变量引用 #

🌐 Environment variable reference

变量默认值描述
SAML_ENABLEDfalse启用 SAML SSO 引擎
SAML_PRIVATE_KEY-Base64 编码的 PKCS#1 RSA 私钥(最少 2048 位)。用于签署 SAML 请求,也可以选择解密断言。
SAML_ALLOW_ENCRYPTED_ASSERTIONSfalse接受来自 IdP 的加密 SAML 断言
SAML_RELAY_STATE_VALIDITY_PERIOD2m0s中继状态令牌的有效时间。如果用户在慢速网络上在 IdP 重定向期间超时,可以增加该值。
SAML_RATE_LIMIT_ASSERTION15最大每秒 ACS 请求数。用于防止断言重放攻击。

故障排除 #

🌐 Troubleshooting

这台服务器没有启用 SAML #

🌐 "SAML is not enabled on this server"

GOTRUE_SAML_ENABLED 变量没有设置为 true,或者 Auth 容器没有检测到更改。确认环境变量通过 docker-compose.yml 传递,并重启:

🌐 The GOTRUE_SAML_ENABLED variable is not set to true, or the Auth container did not pick up the change. Verify the env var is passed through docker-compose.yml and restart:

1
sh run.sh recreate

启动时显示“无效的私钥” #

🌐 "Invalid private key" on startup

GOTRUE_SAML_PRIVATE_KEY 的值格式不正确。请确保它是:

🌐 The GOTRUE_SAML_PRIVATE_KEY value is malformed. Ensure it is:

  • Base64 编码(单行,无换行)
  • 在 PKCS#1 格式(openssl pkey ... -traditional 输出)
  • 至少 2048 位 RSA

如有需要,请重新生成:

🌐 Regenerate if needed:

1
openssl genpkey -algorithm RSA -out pk_pkcs8.pem -quiet && \
2
openssl pkey -in pk_pkcs8.pem -out pk_rsa1.der -outform DER -traditional && \
3
base64 -w 0 -i pk_rsa1.der

身份提供者无法访问 ACS 端点 #

🌐 IdP cannot reach the ACS endpoint

  • 确认 API_EXTERNAL_URL 设置为一个包含 /auth/v1 的 URL,IdP 能访问到(除非本地测试,否则不要用 localhost
  • 检查 API 网关的 /auth/v1/sso/saml/acs/auth/v1/sso/saml/metadata 路由是否配置为开放(没有 key-auth 插件)。
  • 查看 Auth 容器日志:docker compose logs auth

未找到此域的 SSO 提供商 #

🌐 "No SSO provider found for this domain"

  • 确认域名是否已注册:列出提供商并检查 domains 数组
  • 域名匹配是精确且不区分大小写的 - Example.com 匹配 example.com

断言验证失败 #

🌐 Assertion validation fails

  • 确保 IdP 的签名证书与在 Auth 注册的元数据中的一致
  • 如果使用 metadata_url,身份验证会自动刷新过期的元数据(在 ValidUntilCacheDuration 或 24 小时后)。通过更新提供者可以强制刷新。
  • 检查你的服务器和 IdP 之间的时钟同步——SAML 断言有基于时间的有效窗口(NotBefore / NotOnOrAfter

用户已创建,但属性缺失 #

🌐 User is created but attributes are missing

  • 检查你的 attribute_mapping 配置。使用 IdP 的 SAML 断言查看工具(大多数 IdP 都有)来查看发送的确切属性名称。
  • 属性名称在断言中会不区分大小写地与 NameFriendlyName 字段匹配。
  • 映射的属性出现在 user.user_metadata 中。

中继状态已过期 #

🌐 Relay state expired

用户在启动单点登录(SSO)和在身份提供商(IdP)完成认证之间花的时间太长。增加 GOTRUE_SAML_RELAY_STATE_VALIDITY_PERIOD(默认是 2 分钟)。

🌐 The user took too long between initiating SSO and completing authentication at the IdP. Increase GOTRUE_SAML_RELAY_STATE_VALIDITY_PERIOD (default is 2 minutes).

额外资源 #

🌐 Additional resources