跨域身份管理系统(SCIM)
将 SCIM 集成到您的应用程序中。
跨域身份管理系统(SCIM)通过标准化协议,使多域场景中的身份管理变得更容易支持。
此插件公开了一个 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 Bearer Token:请参见生成 SCIM 令牌
生成 SCIM 令牌
在身份提供商开始同步信息到您的 SCIM 服务器之前,您需要生成一个 SCIM 令牌,供身份提供商用于身份验证。
SCIM 令牌是一种简单的 Bearer 令牌,可以通过以下接口生成:
const { data, error } = await authClient.scim.generateToken({ providerId: "acme-corp", // required organizationId: "the-org",});providerIdstringrequired提供商 ID
organizationIdstring可选的组织 ID。指定时,必须启用 organizations 插件
SCIM 令牌总是限制于某个提供商,因此必须指定 providerId。
这可以是您的实例支持的任意提供商(例如内置的 credentials,或通过外部插件如 @better-auth/sso 注册的外部提供商)。
此外,当注册了 organization 插件时,可以通过 organizationId 可选地将令牌限制到某个组织。
重要提示: 默认情况下,任何有权限访问您 better-auth 实例的已认证用户都可以生成 SCIM 令牌。
这对于您的应用特别是在多租户场景下可能构成重要安全风险。
强烈建议您使用钩子限制该功能仅对某些角色或用户开放:
const userRoles = new Set(["admin"]);
const userAdminIds = new Set(["some-admin-user-id"]);
scim({
beforeSCIMTokenGenerated: async ({ user, member, scimToken }) => {
// 重要:使用此钩子限制访问某些角色或用户
// 至少必须限制为管理员用户(见下例)
const userHasAdmin = member?.role && userRoles.has(member.role);
const userIsAdmin = userAdminIds.size > 0 && userAdminIds.has(user.id);
if (!userHasAdmin && !userIsAdmin) {
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" // 可选的组织 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
}
})
]
});启用后,请再次迁移数据库模式。
npx @better-auth/cli migratenpx @better-auth/cli generate请参考 Schema 部分手动添加字段。
管理 SCIM 提供商连接
您可以通过以下端点从应用程序管理 SCIM 提供商连接:
列出 SCIM 提供商连接
列出当前用户作为成员的组织内,或未关联组织的提供商的现有连接。
const { data, error } = await authClient.scim.listProviderConnections();获取 SCIM 提供商连接详情
根据 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格式化姓名(优先于名和姓)
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格式化姓名(优先于名和姓)
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: "任意值" }], // required }, // This endpoint requires a bearer authentication token. headers: { authorization: 'Bearer <token>' },});schemasstring[]required必需的模式声明
OperationsArray<{ op: "replace" | "add" | "remove", path: string, value: any }>requiredJSON 补丁操作列表
删除用户资源
从数据库中彻底删除用户资源。此操作仅能删除与 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:与提供商关联的组织
Schema
该插件需要在 scimProvider 表中添加额外字段以存储提供商的配置。
如果通过 providerOwnership.enabled 启用了提供商所有权:
scimProvider 模式附加以下字段:
配置选项
服务器
providerOwnership:{ enabled: boolean }— 启用时,将每个提供商连接关联至其生成令牌的用户。详见连接所有权。默认{ enabled: false }。
scim({
providerOwnership: { enabled: true },
})defaultSCIM: 默认用于测试的 SCIM 令牌列表。storeSCIMToken: 在数据库中存储 SCIM 令牌的方法,可选encrypted、hashed或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 令牌生成的生命周期:
scim({
beforeSCIMTokenGenerated: async ({ user, member, scimToken }) => {
// 令牌持久化前回调
// 可用于拦截生成过程
if (member?.role !== "admin") {
throw new APIError("FORBIDDEN", { message: "用户权限不足" });
}
},
afterSCIMTokenGenerated: async ({ user, member, scimToken, scimProvider }) => {
// 令牌持久化后回调
// 可用于发送通知或共享令牌
await shareSCIMTokenWithInterestedParty(scimToken);
},
})注意:所有钩子支持错误处理。在 before 钩子抛出错误会阻止操作继续进行。