跨域身份管理系统(SCIM)

将 SCIM 集成到您的应用程序中。

跨域身份管理系统(SCIM)通过标准化协议,使多域场景中的身份管理变得更容易支持。
此插件公开了一个 SCIM 服务器,允许第三方身份提供商将身份同步到您的服务。

安装

安装插件

npm install @better-auth/scim

将插件添加到服务器

auth.ts
import { betterAuth } from "better-auth"
import { scim } from "@better-auth/scim"; 

const auth = betterAuth({
    plugins: [
        scim() 
    ]
})

启用 HTTP 方法

SCIM 需要您的服务器支持 POSTGETPUTPATCHDELETE HTTP 方法。
对于大多数框架,这些方法默认支持,但某些框架可能需要额外配置:

api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";

export const { POST, GET, PUT, PATCH, DELETE } = toNextJsHandler(auth); 
routes/api/auth/*auth.ts
import { auth } from "~/lib/auth";
import { toSolidStartHandler } from "better-auth/solid-start";

export const { GET, POST, PUT, PATCH, DELETE } = toSolidStartHandler(auth); 

迁移数据库

运行迁移或生成模式,为数据库添加必要的字段和表。

npx auth migrate
npx auth generate

也可以参考 Schema 部分手动添加字段。

使用方法

注册后,此插件将公开符合规范的 SCIM 2.0 服务器。
通常,该服务器供第三方(您的身份提供商)使用,需要以下内容:

  • SCIM 基础 URL:应该是 SCIM 服务器的完整 URL(例如 http://your-app.com/api/auth/scim/v2
  • SCIM Bearer Token:请参见生成 SCIM 令牌

生成 SCIM 令牌

在身份提供商开始同步信息到您的 SCIM 服务器之前,您需要生成一个 SCIM 令牌,供身份提供商用于身份验证。

SCIM 令牌是一种简单的 Bearer 令牌,可以通过以下接口生成:

POST/scim/generate-token
const { data, error } = await authClient.scim.generateToken({    providerId: "acme-corp", // required    organizationId: "the-org",});
Parameters
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 连接:

auth.ts
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 管理端点的访问。

auth.ts
import { betterAuth } from "better-auth";
import { scim } from "@better-auth/scim";

const auth = betterAuth({
    plugins: [
        scim({ 
            providerOwnership: { 
                enabled: true
            } 
        }) 
    ]
});

启用后,请再次迁移数据库模式。

npx @better-auth/cli migrate
npx @better-auth/cli generate

请参考 Schema 部分手动添加字段。

管理 SCIM 提供商连接

您可以通过以下端点从应用程序管理 SCIM 提供商连接:

列出 SCIM 提供商连接

列出当前用户作为成员的组织内,或未关联组织的提供商的现有连接。

GET/scim/list-provider-connections
const { data, error } = await authClient.scim.listProviderConnections();

获取 SCIM 提供商连接详情

根据 ID 获取单个连接。仅当用户为该连接所属组织的成员,或该连接未关联组织时允许访问。

GET/scim/get-provider-connection
const { data, error } = await authClient.scim.getProviderConnection({    query: {        providerId: "acme-corp", // required    },});
Parameters
providerIdstringrequired

提供商唯一标识

删除 SCIM 提供商连接

删除现有连接。此操作会立即使关联的令牌失效。

POST/scim/delete-provider-connection
const { data, error } = await authClient.scim.deleteProviderConnection({    providerId: "acme-corp", // required});
Parameters
providerIdstringrequired

提供商唯一标识

SCIM 端点

当前支持规范中的以下子集:

列出用户

获取数据库中可用用户列表。此操作限制为仅列出与您的 SCIM 令牌相同提供商和组织关联的用户。

GET/scim/v2/Users
Notes

返回供应的 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>' },});
Parameters
filterstring

SCIM 合规的筛选表达式

获取用户

获取数据库中的单个用户。仅当用户属于与 SCIM 令牌相同的提供商和组织时返回。

GET/scim/v2/Users/:userId
Notes

返回供应的 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>' },});
Parameters
userIdstringrequired

用户唯一标识

创建新用户

向数据库配置新用户。该用户账户与相同提供商相关联,且为 SCIM 令牌对应同一组织的成员。

POST/scim/v2/Users
Notes

通过 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>' },});
Parameters
externalIdstring

唯一的外部(第三方)标识

nameObject

用户姓名详情

formattedstring

格式化姓名(优先于名和姓)

givenNamestring

familyNamestring

emailsArray<{ value: string, primary?: boolean }>

用户的电子邮件列表,只有一个电子邮件可以是主邮件

更新已有用户

替换数据库中已有用户详情。此操作仅能更新与 SCIM 令牌相同提供商和组织的用户。

PUT/scim/v2/Users/:userId
Notes

通过 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>' },});
Parameters
externalIdstring

唯一的外部(第三方)标识

nameObject

用户姓名详情

formattedstring

格式化姓名(优先于名和姓)

givenNamestring

familyNamestring

emailsArray<{ value: string, primary?: boolean }>

用户的电子邮件列表,只有一个电子邮件可以是主邮件

部分更新已有用户

允许对用户详情进行部分更新。此操作仅能更新与 SCIM 令牌相同提供商和组织的用户。

PATCH/scim/v2/Users/:userId
Notes

部分更新用户资源。参见 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>' },});
Parameters
schemasstring[]required

必需的模式声明

OperationsArray<{ op: "replace" | "add" | "remove", path: string, value: any }>required

JSON 补丁操作列表

删除用户资源

从数据库中彻底删除用户资源。此操作仅能删除与 SCIM 令牌相同提供商和组织的用户。

DELETE/scim/v2/Users/:userId
Notes

删除已有用户资源。参见 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>' },});
Parameters
userIdstringrequired

获取服务提供商配置

获取描述此服务器支持功能的 SCIM 元数据。

GET/scim/v2/ServiceProviderConfig
Notes

身份提供商使用的标准 SCIM 元数据端点。参见 https://datatracker.ietf.org/doc/html/rfc7644#section-4

const data = await auth.api.getSCIMServiceProviderConfig();

获取 SCIM 模式列表

获取支持的 SCIM 模式列表。

GET/scim/v2/Schemas
Notes

身份提供商使用的标准 SCIM 元数据端点,用于获取支持的模式信息。参见 https://datatracker.ietf.org/doc/html/rfc7644#section-4

const data = await auth.api.getSCIMSchemas();

获取 SCIM 模式详情

获取某个支持的 SCIM 模式详情。

GET/scim/v2/Schemas/:schemaId
Notes

身份提供商使用的标准 SCIM 元数据端点,用于获取指定模式信息。参见 https://datatracker.ietf.org/doc/html/rfc7644#section-4

const data = await auth.api.getSCIMSchema();

获取 SCIM 资源类型列表

获取支持的 SCIM 资源类型列表。

GET/scim/v2/ResourceTypes
Notes

身份提供商使用的标准 SCIM 元数据端点,用于获取服务器支持的类型列表。参见 https://datatracker.ietf.org/doc/html/rfc7644#section-4

const data = await auth.api.getSCIMResourceTypes();

获取 SCIM 资源类型详情

获取某个支持的 SCIM 资源类型详情。

GET/scim/v2/ResourceTypes/:resourceTypeId
Notes

身份提供商使用的标准 SCIM 元数据端点,用于获取服务器支持的类型详情。参见 https://datatracker.ietf.org/doc/html/rfc7644#section-4

const data = await auth.api.getSCIMResourceType();

SCIM 属性映射

默认情况下,SCIM 配置会自动映射以下字段:

  • user.email:用户主要邮箱,若无则为第一个可用邮箱
  • user.name:由 namename.formattedname.givenName + name.familyName)派生,若无则回退为用户主要邮箱
  • account.providerId:与 SCIM 令牌关联的提供商
  • account.accountId:默认为 externalId,无则回退为 userName
  • member.organizationId:与提供商关联的组织

Schema

该插件需要在 scimProvider 表中添加额外字段以存储提供商的配置。

Table
字段
类型
描述
id
string
pk
数据库标识符
providerId
string
提供商 ID。用于识别提供商和生成重定向 URL。
scimToken
string
SCIM Bearer 令牌。由您的身份提供商用于向服务器身份验证
organizationId
string
组织 ID。如果提供商关联到组织。

如果通过 providerOwnership.enabled 启用了提供商所有权:

scimProvider 模式附加以下字段:

Table
字段
类型
描述
userId
string
连接所有者的用户 ID。通过 API 生成令牌时自动设置。

配置选项

服务器

  • providerOwnership: { enabled: boolean } — 启用时,将每个提供商连接关联至其生成令牌的用户。详见连接所有权。默认 { enabled: false }
启用连接所有权(需迁移)
scim({
    providerOwnership: { enabled: true },
})
  • defaultSCIM: 默认用于测试的 SCIM 令牌列表。
  • storeSCIMToken: 在数据库中存储 SCIM 令牌的方法,可选 encryptedhashedplain 明文,默认为明文。

您也可以传入自定义加密器或哈希器来存储 SCIM 令牌。

自定义加密器

auth.ts
scim({
    storeSCIMToken: { 
        encrypt: async (scimToken) => {
            return myCustomEncryptor(scimToken);
        },
        decrypt: async (scimToken) => {
            return myCustomDecryptor(scimToken);
        },
    }
})

自定义哈希器

auth.ts
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 钩子抛出错误会阻止操作继续进行。