跨域身份管理系统(SCIM)

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

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

需要自助式 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

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

使用方法

注册后,此插件将公开符合规范的 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 令牌是一种简单的承载令牌,可以通过以下接口生成:

POST/scim/generate-token
const { data, error } = await authClient.scim.generateToken({    providerId: "acme-corp", // required    organizationId: "the-org",});
Parameters
providerIdstringrequired

提供商 ID

organizationIdstring

可选组织 ID。指定时,必须启用组织插件

SCIM 令牌始终限制于某个提供商,因此必须指定 providerId
这可以是您的实例支持的任意提供商(例如内置的 credentials,或通过外部插件如 @better-auth/sso 注册的外部提供商)。
此外,当注册了 organization 插件时,可以通过 organizationId 可选地将令牌限制到某个组织。

重要:个人 SCIM 连接仍可由任何经过身份验证的用户生成。组织范围连接的默认限制是具有 admin 角色或组织创建者角色(organization.creatorRole,默认为 owner)的用户。如果需要不同的策略,请配置 requiredRole 和/或在 钩子 中添加更严格的检查。

组织范围授权

当提供 organizationId 时,Better Auth 要求当前用户是该组织的成员,并且至少拥有一个配置的 requiredRole 值。

默认情况下,requiredRole 解析为:

  • admin
  • organization.creatorRoleowner

相同的角色要求也用于组织范围连接的 SCIM 管理端点:

  • GET /scim/list-provider-connections
  • GET /scim/get-provider-connection
  • POST /scim/delete-provider-connection
auth.ts
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 连接:

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" // 可选的 organization 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
            } 
        }) 
    ]
});

启用时:

  • 个人连接存储创建用户的 userId
  • 仅所有者可以再生、列出、检查或删除这些个人连接
  • 组织范围连接继续使用 requiredRole 配置的组织角色检查

启用后,请确保迁移数据库架构(再次)。

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

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

管理 SCIM 提供商连接

列出 SCIM 提供商连接

列出 SCIM 提供商连接

列出当前用户可以管理的现有连接。对于组织范围连接,用户必须拥有该组织的一个配置的 requiredRole 角色。对于个人连接,当 providerOwnership.enabled 启用时,访问基于所有权。

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

获取 SCIM 提供商连接详情

按提供商 ID 获取单个连接

按提供商 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

格式化名称(优先于 given 和 family 名称)

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

格式化名称(优先于 given 和 family 名称)

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: "any 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 Patch 操作列表

删除用户资源

从数据库中彻底删除用户资源。此操作仅能删除与 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:与提供商关联的组织

模式

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

Table
字段
类型
描述
id
string
PK
A database identifier
providerId
string
-
The provider ID. Used to identify a provider and to generate a redirect URL.
scimToken
string
-
The SCIM bearer token. Used by your identity provider to authenticate against your server
organizationId ?
string
-
The organization Id. If provider is linked to an organization.

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

scimProvider schema 会新增以下字段:

Table
字段
类型
描述
userId ?
string
-
The user id of the connection owner. Set automatically when generating a token via the API.

服务器

  • providerOwnership: { enabled: boolean } — 启用时,将每个提供商连接关联至其生成令牌的用户。详见连接所有权。默认 { enabled: false }
  • requiredRole: string[] — 允许生成组织范围令牌并管理组织范围连接的最小组织角色。默认值为 ["admin", organization.creatorRole ?? "owner"]
仅允许所有者管理组织范围的 SCIM 连接
scim({
    requiredRole: ["owner"],
})
  • providerOwnership: { enabled: boolean } — 当启用时,将每个个人提供商连接与生成其令牌的用户关联。详见连接所有权。默认为 { enabled: false }
启用连接所有权(需要迁移)
scim({
    providerOwnership: { enabled: true },
})
  • defaultSCIM: 默认的 SCIM 令牌列表,用于测试。
  • storeSCIMToken: 存储 SCIM 令牌的方法,可以是 encryptedhashedplain 文本。默认为 plain 文本。

您也可以传入自定义加密器或哈希器来存储 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 令牌生成的生命周期:

注意: 内置的组织角色检查会在这些钩子之前运行。请使用钩子添加更严格的规则,而不是绕过 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 钩子中抛出错误将阻止操作继续执行。