跨域身份管理系统(SCIM)
将 SCIM 集成到您的应用程序中。
跨域身份管理系统(SCIM)通过标准化协议,使多域场景中的身份管理更容易支持。
此插件公开了一个符合规范的 SCIM 2.0 服务器,允许第三方身份提供商将身份同步到您的服务。
需要自助式 SCIM 设置,以便您的客户可以配置自己的身份提供商同步?联系企业版。
安装
安装插件
npm install @better-auth/scim将插件添加到服务器
import { betterAuth } from "better-auth"
import { scim } from "@better-auth/scim";
const auth = betterAuth({
plugins: [
scim()
]
})启用 HTTP 方法
SCIM 需要服务器支持 POST、GET、PUT、PATCH 和 DELETE HTTP 方法。
对于大多数框架,这开箱即用,但某些框架可能需要额外配置:
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET, PUT, PATCH, DELETE } = toNextJsHandler(auth); import { auth } from "~/lib/auth";
import { toSolidStartHandler } from "better-auth/solid-start";
export const { GET, POST, PUT, PATCH, DELETE } = toSolidStartHandler(auth); 使用方法
注册后,此插件将公开符合规范的 SCIM 2.0 服务器。
通常,该服务器供第三方(您的身份提供商)使用,需要以下内容:
- SCIM 基础 URL:这应该是 SCIM 服务器的完全限定 URL(例如
http://your-app.com/api/auth/scim/v2) - SCIM 承载令牌:请参阅生成 SCIM 令牌
自助目录同步
如果您使用 Better Auth Infrastructure,在仪表板中会获得自助目录同步。组织管理员可以创建和管理 SCIM 目录连接,并在不直接调用 SCIM API 的情况下轮换承载令牌。
仪表板可在:
https://dash.better-auth.com/[project]/organization/[orgId]/enterprise从仪表板您可以:
- 创建和移除作用于某个组织的目录连接
- 当您的身份提供商需要轮换时,重新生成 SCIM 承载令牌
这消除了设置 SCIM 时通常需要的来回沟通,将入职时间从数天缩短到数分钟。
生成 SCIM 令牌
在身份提供商开始将信息同步到您的 SCIM 服务器之前,您需要生成一个 SCIM 令牌,让身份提供商用于身份验证。
SCIM 令牌是一种简单的承载令牌,可以通过以下接口生成:
const { data, error } = await authClient.scim.generateToken({ providerId: "acme-corp", // required organizationId: "the-org",});providerIdstringrequired提供商 ID
organizationIdstring可选组织 ID。指定时,必须启用组织插件
SCIM 令牌始终限制于某个提供商,因此必须指定 providerId。
这可以是您的实例支持的任意提供商(例如内置的 credentials,或通过外部插件如 @better-auth/sso 注册的外部提供商)。
此外,当注册了 organization 插件时,可以通过 organizationId 可选地将令牌限制到某个组织。
重要:个人 SCIM 连接仍可由任何经过身份验证的用户生成。组织范围连接的默认限制是具有 admin 角色或组织创建者角色(organization.creatorRole,默认为 owner)的用户。如果需要不同的策略,请配置 requiredRole 和/或在 钩子 中添加更严格的检查。
组织范围授权
当提供 organizationId 时,Better Auth 要求当前用户是该组织的成员,并且至少拥有一个配置的 requiredRole 值。
默认情况下,requiredRole 解析为:
adminorganization.creatorRole或owner
相同的角色要求也用于组织范围连接的 SCIM 管理端点:
GET /scim/list-provider-connectionsGET /scim/get-provider-connectionPOST /scim/delete-provider-connection
import { betterAuth } from "better-auth";
import { scim } from "@better-auth/scim";
const auth = betterAuth({
plugins: [
scim({
beforeSCIMTokenGenerated: async ({ user }) => {
// 在内置的组织角色检查之上添加更严格的规则。
if (!approvedScimOperators.has(user.id)) {
throw new APIError("FORBIDDEN", { message: "用户权限不足" });
}
},
})
]
});有关支持的钩子详细信息,请参见钩子文档。
默认 SCIM 令牌
我们同样提供了一种方法,允许您指定默认使用的 SCIM 令牌。
这方便您在数据库未设置提供商时测试 SCIM 连接:
import { betterAuth } from "better-auth"
import { scim } from "@better-auth/scim";
const auth = betterAuth({
plugins: [
scim({
defaultSCIM: [
{
providerId: "default-scim", // 要预配置的现有提供商 ID
scimToken: "some-scim-token", // SCIM 明文令牌
organizationId: "the-org" // 可选的 organization ID
}
]
})
]
});重要:请注意,您必须对 scimToken 进行 base64 编码,才能按如下方式使用:base64(scimToken:providerId[:organizationId])。
在上面的示例中,您需要对 some-scim-token:default-scim:the-org 文本进行 base64 编码,得到的 scimToken 为:c29tZS1zY2ltLXRva2VuOmRlZmF1bHQtc2NpbTp0aGUtb3Jn
SCIM 提供商连接所有权
SCIM 提供商连接所有权适用于个人(非组织)SCIM 连接。它允许您的应用程序跟踪谁生成了连接,并将该连接后续管理操作限制为同一用户。
import { betterAuth } from "better-auth";
import { scim } from "@better-auth/scim";
const auth = betterAuth({
plugins: [
scim({
providerOwnership: {
enabled: true
}
})
]
});启用时:
- 个人连接存储创建用户的
userId - 仅所有者可以再生、列出、检查或删除这些个人连接
- 组织范围连接继续使用
requiredRole配置的组织角色检查
启用后,请确保迁移数据库架构(再次)。
npx @better-auth/cli migratenpx @better-auth/cli generate请参考 架构 部分手动添加字段。
管理 SCIM 提供商连接
列出 SCIM 提供商连接
列出 SCIM 提供商连接
列出当前用户可以管理的现有连接。对于组织范围连接,用户必须拥有该组织的一个配置的 requiredRole 角色。对于个人连接,当 providerOwnership.enabled 启用时,访问基于所有权。
const { data, error } = await authClient.scim.listProviderConnections();获取 SCIM 提供商连接详情
按提供商 ID 获取单个连接
按提供商 ID 获取单个连接。仅当用户可以管理该连接时才允许访问:要么因为他们满足配置的组织角色要求,要么因为他们拥有个人连接。
const { data, error } = await authClient.scim.getProviderConnection({ query: { providerId: "acme-corp", // required },});providerIdstringrequired唯一的提供商标识符
删除 SCIM 提供商连接
删除现有连接
删除现有连接。此操作会立即使关联的令牌失效。
const { data, error } = await authClient.scim.deleteProviderConnection({ providerId: "acme-corp", // required});providerIdstringrequired唯一的提供商标识符
SCIM 端点
当前支持规范中的以下子集:
列出用户
获取数据库中可用用户列表。此操作限制为仅列出与您的 SCIM 令牌相同提供商和组织关联的用户。
返回配置的 SCIM 用户详细信息。请参考 https://datatracker.ietf.org/doc/html/rfc7644#section-3.4.1
const data = await auth.api.listSCIMUsers({ query: { filter: 'userName eq "user-a"', }, // This endpoint requires a bearer authentication token. headers: { authorization: 'Bearer <token>' },});filterstringSCIM 兼容的过滤表达式
获取用户
获取数据库中的单个用户。仅当用户属于与 SCIM 令牌相同的提供商和组织时返回。
返回配置的 SCIM 用户详细信息。请参考 https://datatracker.ietf.org/doc/html/rfc7644#section-3.4.1
const data = await auth.api.getSCIMUser({ params: { userId: "user id", // required }, // This endpoint requires a bearer authentication token. headers: { authorization: 'Bearer <token>' },});userIdstringrequired唯一用户标识符
创建新用户
向数据库配置新用户。该用户账户与相同提供商相关联,且为 SCIM 令牌对应同一组织的成员。
通过 SCIM 配置新用户。请参考 https://datatracker.ietf.org/doc/html/rfc7644#section-3.3
const data = await auth.api.createSCIMUser({ body: { externalId: "third party id", name: { formatted: "Daniel Perez", givenName: "Daniel", familyName: "Perez", }, emails: [{ value: "daniel@email.com", primary: true }], }, // This endpoint requires a bearer authentication token. headers: { authorization: 'Bearer <token>' },});externalIdstring唯一的第三方(外部)标识符
nameObject用户名称详情
formattedstring格式化名称(优先于 given 和 family 名称)
givenNamestring名字
familyNamestring姓氏
emailsArray<{ value: string, primary?: boolean }>与用户关联的电子邮件列表,只有一个可以是主要电子邮件
更新已有用户
替换数据库中已有用户详情。此操作仅能更新与 SCIM 令牌相同提供商和组织的用户。
通过 SCIM 更新已有用户。请参考 https://datatracker.ietf.org/doc/html/rfc7644#section-3.3
const data = await auth.api.updateSCIMUser({ body: { externalId: "third party id", name: { formatted: "Daniel Perez", givenName: "Daniel", familyName: "Perez", }, emails: [{ value: "daniel@email.com", primary: true }], }, // This endpoint requires a bearer authentication token. headers: { authorization: 'Bearer <token>' },});externalIdstring唯一的第三方(外部)标识符
nameObject用户名称详情
formattedstring格式化名称(优先于 given 和 family 名称)
givenNamestring名字
familyNamestring姓氏
emailsArray<{ value: string, primary?: boolean }>与用户关联的电子邮件列表,只有一个可以是主要电子邮件
部分更新已有用户
允许对用户详情进行部分更新。此操作仅能更新与 SCIM 令牌相同提供商和组织的用户。
部分更新用户资源。请参考 https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2
const data = await auth.api.patchSCIMUser({ body: { schemas: ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], // required Operations: [{ op: "replace", path: "/userName", value: "any value" }], // required }, // This endpoint requires a bearer authentication token. headers: { authorization: 'Bearer <token>' },});schemasstring[]required必需的模式声明
OperationsArray<{ op: "replace" | "add" | "remove", path: string, value: any }>requiredJSON Patch 操作列表
删除用户资源
从数据库中彻底删除用户资源。此操作仅能删除与 SCIM 令牌相同提供商和组织的用户。
删除现有的用户资源。请参考 https://datatracker.ietf.org/doc/html/rfc7644#section-3.6
const data = await auth.api.deleteSCIMUser({ params: { userId, // required }, // This endpoint requires a bearer authentication token. headers: { authorization: 'Bearer <token>' },});userIdstringrequired获取服务提供商配置
获取描述此服务器支持功能的 SCIM 元数据。
标准 SCIM 元数据端点,由身份提供商使用。请参考 https://datatracker.ietf.org/doc/html/rfc7644#section-4
const data = await auth.api.getSCIMServiceProviderConfig();获取 SCIM 模式列表
获取支持的 SCIM 模式列表。
标准 SCIM 元数据端点,由身份提供商使用以获取支持的模式信息。请参考 https://datatracker.ietf.org/doc/html/rfc7644#section-4
const data = await auth.api.getSCIMSchemas();获取 SCIM 模式详情
获取某个支持的 SCIM 模式详情。
标准 SCIM 元数据端点,由身份提供商使用以获取给定模式的信息。请参考 https://datatracker.ietf.org/doc/html/rfc7644#section-4
const data = await auth.api.getSCIMSchema();获取 SCIM 资源类型列表
获取支持的 SCIM 资源类型列表。
标准 SCIM 元数据端点,由身份提供商使用以获取服务器支持的类型列表。请参考 https://datatracker.ietf.org/doc/html/rfc7644#section-4
const data = await auth.api.getSCIMResourceTypes();获取 SCIM 资源类型详情
获取某个支持的 SCIM 资源类型详情。
标准 SCIM 元数据端点,由身份提供商使用以获取服务器支持的类型详情。请参考 https://datatracker.ietf.org/doc/html/rfc7644#section-4
const data = await auth.api.getSCIMResourceType();SCIM 属性映射
默认情况下,SCIM 配置会自动映射以下字段:
user.email:用户的主电子邮件,如果没有主电子邮件,则使用第一个可用的电子邮件user.name:从name(name.formatted或name.givenName+name.familyName)派生,如果没有则回退到用户的主电子邮件account.providerId:与SCIM令牌关联的提供商account.accountId:默认为externalId,如果没有则回退到userNamemember.organizationId:与提供商关联的组织
模式
该插件需要在 scimProvider 表中添加额外字段以存储提供商的配置。
如果通过 providerOwnership.enabled 启用了提供商所有权:
scimProvider schema 会新增以下字段:
服务器
providerOwnership:{ enabled: boolean }— 启用时,将每个提供商连接关联至其生成令牌的用户。详见连接所有权。默认{ enabled: false }。
requiredRole:string[]— 允许生成组织范围令牌并管理组织范围连接的最小组织角色。默认值为["admin", organization.creatorRole ?? "owner"]。
scim({
requiredRole: ["owner"],
})providerOwnership:{ enabled: boolean }— 当启用时,将每个个人提供商连接与生成其令牌的用户关联。详见连接所有权。默认为{ enabled: false }。
scim({
providerOwnership: { enabled: true },
})defaultSCIM: 默认的 SCIM 令牌列表,用于测试。storeSCIMToken: 存储 SCIM 令牌的方法,可以是encrypted、hashed或plain文本。默认为plain文本。
您也可以传入自定义加密器或哈希器来存储 SCIM 令牌。
自定义加密器
scim({
storeSCIMToken: {
encrypt: async (scimToken) => {
return myCustomEncryptor(scimToken);
},
decrypt: async (scimToken) => {
return myCustomDecryptor(scimToken);
},
}
})自定义哈希器
scim({
storeSCIMToken: {
hash: async (scimToken) => {
return myCustomHasher(scimToken);
},
}
})钩子
以下钩子允许拦截 SCIM 令牌生成的生命周期:
注意: 内置的组织角色检查会在这些钩子之前运行。请使用钩子添加更严格的规则,而不是绕过 requiredRole。
const approvedScimOperators = new Set(["some-admin-user-id"]);
scim({
beforeSCIMTokenGenerated: async ({ user, member, scimToken }) => {
// 对于个人连接,`member` 为 null。
// 在令牌持久化之前添加您需要的任何额外限制。
if (!approvedScimOperators.has(user.id)) {
throw new APIError("FORBIDDEN", { message: "User does not have enough permissions" });
}
},
afterSCIMTokenGenerated: async ({ user, member, scimToken, scimProvider }) => {
// 令牌持久化后回调
// 可用于发送通知或共享令牌
await shareSCIMTokenWithInterestedParty(scimToken);
},
})注意:所有钩子都支持错误处理。在 before 钩子中抛出错误将阻止操作继续执行。