跨域身份管理系统(SCIM)
跨域身份管理系统(SCIM)通过标准化协议使在多域场景中管理身份变得更加容易。该插件提供了一个 SCIM 服务器,允许第三方身份提供者将身份同步到你的服务。
【System for Cross-domain Identity Management (SCIM) makes managing identities in multi-domain scenarios easier to support via a standardized protocol. This plugin exposes a SCIM server that allows third party identity providers to sync identities to your service.】
安装
【Installation】
安装插件
npm install @better-auth/scimAdd Plugin to the server
import { betterAuth } from "better-auth"
import { scim } from "@better-auth/scim";
const auth = betterAuth({
plugins: [
scim()
]
})Enable HTTP methods
SCIM 要求你的服务器支持 POST、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); Migrate the database
运行迁移或生成架构,以向数据库添加必要的字段和表。
npx @better-auth/cli migratenpx @better-auth/cli generateSee the Schema section to add the fields manually.
用法
【Usage】
注册后,该插件将公开符合 SCIM 2.0 的服务器。通常,该服务器用于供第三方(你的身份提供者)使用,并将需要一个:
【Upon registration, this plugin will expose compliant SCIM 2.0 server. Generally, this server is meant to be consumed by a third-party (your identity provider), and will require a:】
- SCIM 基本 URL:这应该是指向 SCIM 服务器的完整 URL(例如
http://your-app.com/api/auth/scim/v2) - SCIM 访问令牌:请参阅 生成 SCIM 令牌
生成 SCIM 令牌
【Generating a SCIM token】
在你的身份提供者可以开始将信息同步到你的 SCIM 服务器之前,你需要生成一个 SCIM 令牌,你的身份提供者将使用该令牌进行身份验证。
【Before your identity provider can start syncing information to your SCIM server, you need to generate a SCIM token that your identity provider will use to authenticate against it.】
SCIM 令牌是一种可以生成的简单承载令牌:
【A SCIM token is a simple bearer token that you can generate:】
const { data, error } = await authClient.scim.generateToken({ providerId: "acme-corp", // required organizationId: "the-org",});| Prop | Description | Type |
|---|---|---|
providerId | The provider id | string |
organizationId? | Optional organization id. When specified, the organizations plugin must also be enabled | string |
SCIM 令牌总是限制于特定的提供者,因此你需要指定一个 providerId。这个提供者可以是你的实例支持的任何提供者(例如内置提供者之一,如 credentials,或者通过外部插件注册的外部提供者,例如 @better-auth/sso)。
此外,当注册了 organization 插件时,你可以选择通过 organizationId 将令牌限制在某个组织内。
【A SCIM token is always restricted to a provider, thus you are required to specify a providerId. This can be any provider your instance supports (e.g one of the built-in providers such as credentials or an external provider registered through an external plugin such as @better-auth/sso).
Additionally, when the organization plugin is registered, you can optionally restrict the token to an organization via the organizationId.】
重要提示: 默认情况下,任何拥有访问你 better-auth 实例权限的经过身份验证的用户都可以生成 SCIM 令牌。这可能对你的应用构成重要的安全风险,特别是在多租户场景下。 强烈建议你实现 hooks 来将此访问权限限制为特定角色或用户:
const userRoles = new Set(["admin"]);
const userAdminIds = new Set(["some-admin-user-id"]);
scim({
beforeSCIMTokenGenerated: async ({ user, member, scimToken }) => {
// IMPORTANT: Use this hook to restrict access to certain roles or users
// At the very least access must be restricted to admin users (see example below)
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: "User does not have enough permissions" });
}
},
})请参阅 hooks 文档以获取有关支持的钩子(hooks)的更多详细信息。
【See the hooks documentation for more details about supported hooks.】
默认 SCIM 令牌
【Default SCIM token】
我们还提供了一种方法,让你可以指定默认使用的 SCIM 令牌。这使你可以在不在数据库中设置提供程序的情况下测试 SCIM 连接:
【We also provide a way for you to specify a SCIM token to use by default. This allows you to test a SCIM connection without setting up providers in the database:】
const auth = betterAuth({
plugins: [
scim({
defaultSCIM: [
{
providerId: "default-scim", // ID of the existing provider you want to provision
scimToken: "some-scim-token", // SCIM plain token
organizationId: "the-org" // Optional organization id
}
]
})
]
});重要:请注意,在尝试使用之前,你必须先对 scimToken 进行 Base64 编码,方式如下:base64(scimToken:providerId[:organizationId])。
在上面的例子中,你需要将 some-scim-token:default-scim:the-org 文本编码为 base64,生成以下 scimToken:c29tZS1zY2ltLXRva2VuOmRlZmF1bHQtc2NpbTp0aGUtb3Jn
SCIM 端点
【SCIM endpoints】
当前支持规范的以下子集:
【The following subset of the specification is currently supported:】
列出用户
【List users】
获取数据库中可用用户的列表。此操作仅限列出与你的 SCIM 令牌所属的同一提供商和组织相关的用户。
【Get a list of available users in the database. This is restricted to list only users associated to the same provider and organization than your SCIM token.】
Returns the provisioned SCIM user details. See 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>' },});| Prop | Description | Type |
|---|---|---|
filter? | SCIM compliant filter expression | string |
获取用户
【Get user】
从数据库中获取用户。只有当用户属于与 SCIM 令牌相同的提供者和组织时,才会返回该用户。
【Get an user from the database. The user will be only returned if it belongs to the same provider and organization than the SCIM token.】
Returns the provisioned SCIM user details. See 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>' },});| Prop | Description | Type |
|---|---|---|
userId | Unique user identifier | string |
创建新用户
【Create new user】
为数据库提供一个新用户。该用户将拥有一个与同一提供商关联的账户,并且将成为与 SCIM 令牌相同组织的成员。
【Provisions a new user to the database. The user will have an account associated to the same provider and will be member of the same org than the SCIM token.】
Provision a new user via SCIM. See 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>' },});| Prop | Description | Type |
|---|---|---|
externalId? | Unique external (third party) identifier | string |
name? | User name details | Object |
name.formatted? | Formatted name (takes priority over given and family name) | string |
name.givenName? | Given name | string |
name.familyName? | Family name | string |
emails? | List of emails associated to the user, only a single email can be primary | Array<{ value: string, primary?: boolean }> |
更新现有用户
【Update an existing user】
替换数据库中现有的用户详细信息。此操作只能更新属于与 SCIM 令牌相同提供商和组织的用户。
【Replaces an existing user details in the database. This operation can only update users that belong to the same provider and organization than the SCIM token.】
Updates an existing user via SCIM. See 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>' },});| Prop | Description | Type |
|---|---|---|
externalId? | Unique external (third party) identifier | string |
name? | User name details | Object |
name.formatted? | Formatted name (takes priority over given and family name) | string |
name.givenName? | Given name | string |
name.familyName? | Family name | string |
emails? | List of emails associated to the user, only a single email can be primary | Array<{ value: string, primary?: boolean }> |
部分更新现有用户
【Partial update an existing user】
允许对用户详细信息进行部分更新。此操作只能更新与 SCIM 令牌属于同一提供商和组织的用户。
【Allows to apply a partial update to the user details. This operation can only update users that belong to the same provider and organization than the SCIM token.】
Partially updates a user resource. See 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>' },});| Prop | Description | Type |
|---|---|---|
schemas | Mandatory schema declaration | string[] |
Operations | List of JSON patch operations | Array<{ op: "replace" | "add" | "remove", path: string, value: any }> |
删除用户资源
【Deletes a user resource】
完全从数据库中删除用户资源。此操作仅能删除与 SCIM 令牌属于相同提供者和组织的用户。
【Completely deletes a user resource from the database. This operation can only delete users that belong to the same provider and organization than the SCIM token.】
Deletes an existing user resource. See 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>' },});| Prop | Description | Type |
|---|---|---|
userId | string |
获取服务提供商配置
【Get service provider config】
获取描述此服务器支持功能的 SCIM 元数据。
【Get SCIM metadata describing supported features of this server.】
Standard SCIM metadata endpoint used by identity providers. See https://datatracker.ietf.org/doc/html/rfc7644#section-4
const data = await auth.api.getSCIMServiceProviderConfig();获取 SCIM 架构
【Get SCIM schemas】
获取受支持的 SCIM 模式列表。
【Get the list of supported SCIM schemas.】
Standard SCIM metadata endpoint used by identity providers to acquire information about supported schemas. See https://datatracker.ietf.org/doc/html/rfc7644#section-4
const data = await auth.api.getSCIMSchemas();获取 SCIM 架构
【Get SCIM schema】
获取受支持的 SCIM 模式的详细信息。
【Get the details of a supported SCIM schema.】
Standard SCIM metadata endpoint used by identity providers to acquire information about a given schema. See https://datatracker.ietf.org/doc/html/rfc7644#section-4
const data = await auth.api.getSCIMSchema();获取 SCIM 资源类型
【Get SCIM resource types】
获取受支持的 SCIM 类型列表。
【Get the list of supported SCIM types.】
Standard SCIM metadata endpoint used by identity providers to get a list of server supported types. See https://datatracker.ietf.org/doc/html/rfc7644#section-4
const data = await auth.api.getSCIMResourceTypes();获取 SCIM 资源类型
【Get SCIM resource type】
获取受支持的 SCIM 资源类型的详细信息。
【Get the details of a supported SCIM resource type.】
Standard SCIM metadata endpoint used by identity providers to get a server supported type. See https://datatracker.ietf.org/doc/html/rfc7644#section-4
const data = await auth.api.getSCIMResourceType();SCIM 属性映射
【SCIM attribute mapping】
默认情况下,SCIM 配置将自动映射以下字段:
【By default, the SCIM provisioning will automatically map the following fields:】
user.email:用户主要邮箱,如果没有主要邮箱,则为第一个可用邮箱user.name:源自name(name.formatted或name.givenName+name.familyName),若无则使用用户主电子邮件account.providerId:与SCIM令牌关联的提供商account.accountId:默认为externalId,如果没有则使用userNamemember.organizationId:与提供者关联的组织
架构
【Schema】
该插件需要在 scimProvider 表中添加额外的字段以存储提供商的配置。
【The plugin requires additional fields in the scimProvider table to store the provider's configuration.】
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | 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. |
选项
【Options】
服务器
【Server】
defaultSCIM:用于测试的默认 SCIM 令牌列表。storeSCIMToken:在数据库中存储 SCIM 令牌的方法,无论是encrypted、hashed还是plain文本。默认是plain文本。
或者,你可以传入自定义的加密器或哈希器,将 SCIM 令牌存储在你的数据库中。
【Alternatively, you can pass a custom encryptor or hasher to store the SCIM token in your database.】
自定义加密器
scim({
storeSCIMToken: {
encrypt: async (scimToken) => {
return myCustomEncryptor(scimToken);
},
decrypt: async (scimToken) => {
return myCustomDecryptor(scimToken);
},
}
})自定义哈希器
scim({
storeSCIMToken: {
hash: async (scimToken) => {
return myCustomHasher(scimToken);
},
}
})钩子
【Hooks】
以下钩子允许拦截 SCIM 令牌生成的生命周期:
【The following hooks allow to intercept the lifecycle of the SCIM token generation:】
scim({
beforeSCIMTokenGenerated: async ({ user, member, scimToken }) => {
// Callback called before the scim token is persisted
// can be useful to intercept the generation
if (member?.role !== "admin") {
throw new APIError("FORBIDDEN", { message: "User does not have enough permissions" });
}
},
afterSCIMTokenGenerated: async ({ user, member, scimToken, scimProvider }) => {
// Callback called after the scim token has been persisted
// can be useful to send a notification or otherwise share the token
await shareSCIMTokenWithInterestedParty(scimToken);
},
})注意:所有钩子都支持错误处理。在 before 钩子中抛出错误将阻止操作继续进行。