OIDC 提供程序
OIDC 提供者插件 使您能够构建和管理自己的 OpenID Connect (OIDC) 提供者,从而完全掌控用户身份验证,无需依赖 Okta 或 Azure AD 等第三方服务。它还允许其他服务通过您的 OIDC 提供者进行用户身份验证。
主要功能:
- 客户端注册:注册客户端以通过您的 OIDC 提供者进行身份验证。
- 动态客户端注册:允许客户端动态注册。
- 受信任客户端:配置硬编码的受信任客户端,可选择绕过用户同意流程。
- 授权码流程:支持授权码流程(Authorization Code Flow)。
- 公共客户端:支持 SPA、移动应用、CLI 工具等的公共客户端。
- JWKS 端点:发布 JWKS 端点,允许客户端验证令牌。(尚未完全实现)
- 刷新令牌:签发刷新令牌,并使用
refresh_token授权类型处理访问令牌的续期。 - OAuth 同意界面:实现用于用户授权的 OAuth 同意界面,并为受信任应用提供绕过同意的选项。
- 用户信息端点:提供用户信息端点(UserInfo endpoint),供客户端获取用户详细信息。
此插件正在积极开发中,可能不适合生产环境使用。请在 GitHub 上报告任何问题或错误。
安装
挂载插件
将 OIDC 插件添加到您的 auth 配置中。有关如何配置插件,请参阅 OIDC 配置。
import { betterAuth } from "better-auth";
import { oidcProvider } from "better-auth/plugins";
const auth = betterAuth({
plugins: [oidcProvider({
loginPage: "/sign-in", // 登录页面的路径
// ...其他选项
})]
})迁移数据库
运行迁移或生成模式,以向数据库添加必要的字段和表。
npx @better-auth/cli migratenpx @better-auth/cli generate有关手动添加字段,请参阅 Schema 部分。
添加客户端插件
将 OIDC 客户端插件添加到您的 auth 客户端配置中。
import { createAuthClient } from "better-auth/client";
import { oidcClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [oidcClient({
// 您的 OIDC 配置
})]
})使用
安装完成后,您可以使用 OIDC Provider 来管理应用程序内的认证流程。
注册新客户端
要注册新的 OIDC 客户端,请使用 oauth2.register 方法。
简单示例
const application = await client.oauth2.register({
client_name: "My Client",
redirect_uris: ["https://client.example.com/callback"],
});完整方法
const { data, error } = await authClient.oauth2.register({ redirect_uris: ["https://client.example.com/callback"], // required token_endpoint_auth_method: "client_secret_basic", grant_types: ["authorization_code"], response_types: ["code"], client_name: "My App", client_uri: "https://client.example.com", logo_uri: "https://client.example.com/logo.png", scope: "profile email", contacts: ["admin@example.com"], tos_uri: "https://client.example.com/tos", policy_uri: "https://client.example.com/policy", jwks_uri: "https://client.example.com/jwks", jwks: {"keys": [{"kty": "RSA", "alg": "RS256", "use": "sig", "n": "...", "e": "..."}]}, metadata: {"key": "value"}, software_id: "my-software", software_version: "1.0.0", software_statement,});| 属性 | 描述 | 类型 |
|---|---|---|
redirect_uris | 重定向 URI 列表 | string[] |
token_endpoint_auth_method? | 令牌端点的认证方法 | "none" | "client_secret_basic" | "client_secret_post" |
grant_types? | 应用支持的授权类型 | ("authorization_code" | "implicit" | "password" | "client_credentials" | "refresh_token" | "urn:ietf:params:oauth:grant-type:jwt-bearer" | "urn:ietf:params:oauth:grant-type:saml2-bearer")[] |
response_types? | 应用支持的响应类型 | ("code" | "token")[] |
client_name? | 应用名称 | string |
client_uri? | 应用 URI | string |
logo_uri? | 应用 Logo URI | string |
scope? | 应用支持的作用域。使用空格分隔 | string |
contacts? | 应用的联系信息 | string[] |
tos_uri? | 应用服务条款 URI | string |
policy_uri? | 应用隐私政策 URI | string |
jwks_uri? | 应用 JWKS URI | string |
jwks? | 应用的 JWKS | Record<string, any> |
metadata? | 应用的元数据 | Record<string, any> |
software_id? | 应用的软件 ID | string |
software_version? | 应用的软件版本 | string |
software_statement? | 应用的软件声明 | string |
此端点支持符合 RFC7591 规范的客户端注册。
应用创建后,您将收到可以展示给用户的 client_id 和 client_secret。
此端点支持符合 RFC7591 规范的客户端注册。
可信客户端
对于第一方应用程序和内部服务,您可以直接在 OIDC 提供者配置中配置可信客户端。可信客户端绕过数据库查询以获得更好的性能,并且可以选择跳过同意屏幕以改善用户体验。
import { betterAuth } from "better-auth";
import { oidcProvider } from "better-auth/plugins";
const auth = betterAuth({
plugins: [
oidcProvider({
loginPage: "/sign-in",
trustedClients: [
{
clientId: "internal-dashboard",
clientSecret: "secure-secret-here",
name: "Internal Dashboard",
type: "web",
redirectURLs: ["https://dashboard.company.com/auth/callback"],
disabled: false,
skipConsent: true, // 为此可信客户端跳过同意步骤
metadata: { internal: true }
},
{
clientId: "mobile-app",
clientSecret: "mobile-secret",
name: "Company Mobile App",
type: "native",
redirectURLs: ["com.company.app://auth"],
disabled: false,
skipConsent: false, // 如有需要仍要求同意
metadata: {}
}
]
})]
})用户信息端点
OIDC 提供程序包含一个用户信息端点,允许客户端检索已认证用户的信息。该端点位于 /oauth2/userinfo,需要有效的访问令牌。
// 客户端使用用户信息端点的示例
const response = await fetch('https://your-domain.com/api/auth/oauth2/userinfo', {
headers: {
'Authorization': 'Bearer ACCESS_TOKEN'
}
});
const userInfo = await response.json();
// userInfo 包含基于授权范围的用户详细信息用户信息端点根据授权期间授予的范围返回不同的声明:
- 使用
openid范围:返回用户 ID(sub声明) - 使用
profile范围:返回姓名、图片、名、姓 - 使用
email范围:返回邮箱和邮箱验证状态
getAdditionalUserInfoClaim 函数接收用户对象、请求的范围数组和客户端,允许您根据授权期间授予的范围有条件地包含声明。这些附加声明将包含在用户信息端点响应和 ID 令牌中。
同意屏幕
当用户被重定向到 OIDC 提供程序进行身份验证时,可能会提示他们授权应用程序访问其数据。这被称为同意屏幕。默认情况下,Better Auth 会显示一个示例同意屏幕。您可以在初始化时提供 consentPage 选项来自定义同意屏幕。
注意:对于设置了 skipConsent: true 的可信客户端,将完全跳过同意页面,为第一方应用提供无缝体验。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
plugins: [oidcProvider({
consentPage: "/path/to/consent/page"
})]
})该插件会将用户重定向到指定路径,并在 URL 中附带 consent_code、client_id 和 scope 查询参数。您可以使用这些信息展示自定义的同意页面。一旦用户同意授权,您可以调用 oauth2.consent 来完成授权流程。
同意端点支持两种传递授权码的方式:
方法一:URL 参数
// 从 URL 中获取授权码
const params = new URLSearchParams(window.location.search);
// 在请求体中提交授权码以完成同意操作
const consentCode = params.get('consent_code');
if (!consentCode) {
throw new Error('URL 参数中未找到授权码');
}
const res = await client.oauth2.consent({
accept: true, // 若拒绝则设为 false
consent_code: consentCode,
});方法二:基于 Cookie
// 授权码会自动存储在已签名的 Cookie 中
// 只需提交同意决定即可
const res = await client.oauth2.consent({
accept: true, // 若拒绝则设为 false
// 使用基于 Cookie 的流程时无需提供 consent_code
});两种方法都得到完整支持。URL 参数方式适用于移动应用和第三方场景,而基于 Cookie 的方式为 Web 应用提供了更简洁的实现。
处理登录流程
当用户被重定向至 OIDC 提供商进行身份验证时,若尚未登录,系统将自动跳转至登录页面。您可以在初始化时通过配置 loginPage 选项来自定义登录页面的路径。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
plugins: [oidcProvider({
loginPage: "/sign-in"
})]
})您无需进行额外处理;当新会话创建时,插件将自动接管并继续完成授权流程。
配置选项
OIDC 元数据
通过在初始化时提供配置对象,您可以自定义 OIDC 元数据。
import { betterAuth } from "better-auth";
import { oidcProvider } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [oidcProvider({
metadata: {
issuer: "https://your-domain.com",
authorization_endpoint: "/custom/oauth2/authorize",
token_endpoint: "/custom/oauth2/token",
// ...其他自定义元数据项
}
})]
})JWKS 端点
OIDC Provider 插件可以与 JWT 插件集成,为 ID 令牌提供非对称密钥签名,这些令牌可在 JWKS 端点上进行验证。
要使您的插件符合 OIDC 标准,您必须禁用 /token 端点,因为 OAuth 的等效端点位于 /oauth2/token。
import { betterAuth } from "better-auth";
import { oidcProvider } from "better-auth/plugins";
import { jwt } from "better-auth/plugins";
export const auth = betterAuth({
disabledPaths: [
"/token",
],
plugins: [
jwt(), // 确保添加 JWT 插件
oidcProvider({
useJWTPlugin: true, // 启用 JWT 插件集成
loginPage: "/sign-in",
// ... 其他选项
})
]
})当 useJWTPlugin: false(默认值)时,ID 令牌将使用应用程序密钥进行签名。
动态客户端注册
如果您希望允许客户端动态注册,可以通过将 allowDynamicClientRegistration 选项设置为 true 来启用此功能。
const auth = betterAuth({
plugins: [oidcProvider({
allowDynamicClientRegistration: true,
})]
})这将允许客户端通过公开可用的 /register 端点进行注册。
数据库模式
OIDC Provider 插件向数据库添加以下表:
OAuth 应用
表名:oauthApplication
| 字段名称 | 类型 | Key | 描述 |
|---|---|---|---|
| id | string | OAuth 客户端的数据库 ID | |
| clientId | string | 每个 OAuth 客户端的唯一标识符 | |
| clientSecret | string | OAuth 客户端的密钥。对于使用 PKCE 的公共客户端是可选的。 | |
| name | string | - | OAuth 客户端的名称 |
| redirectURLs | string | - | 重定向 URL 的逗号分隔列表 |
| metadata | string | OAuth 客户端的附加元数据 | |
| type | string | - | OAuth 客户端类型(例如:web、mobile) |
| disabled | boolean | - | 指示客户端是否被禁用 |
| userId | string | 拥有该客户端的用户 ID(可选) | |
| createdAt | Date | - | OAuth 客户端创建时的时间戳 |
| updatedAt | Date | - | OAuth 客户端最后更新时间的时间戳 |
OAuth 访问令牌
表名:oauthAccessToken
| 字段名称 | 类型 | Key | 描述 |
|---|---|---|---|
| id | string | 访问令牌的数据库 ID | |
| accessToken | string | - | 颁发给客户端的访问令牌 |
| refreshToken | string | - | 颁发给客户端的刷新令牌 |
| accessTokenExpiresAt | Date | - | 访问令牌的过期时间 |
| refreshTokenExpiresAt | Date | - | 刷新令牌的过期时间 |
| clientId | string | OAuth 客户端的 ID | |
| userId | string | 与令牌关联的用户 ID | |
| scopes | string | - | 授予的权限范围列表(逗号分隔) |
| createdAt | Date | - | 访问令牌创建时间戳 |
| updatedAt | Date | - | 访问令牌最后更新时间戳 |
OAuth 授权同意
表名: oauthConsent
| 字段名称 | 类型 | Key | 描述 |
|---|---|---|---|
| id | string | 授权同意的数据库 ID | |
| userId | string | 给予授权的用户 ID | |
| clientId | string | OAuth 客户端 ID | |
| scopes | string | - | 已授权范围的逗号分隔列表 |
| consentGiven | boolean | - | 指示是否已给予授权 |
| createdAt | Date | - | 授权给予的时间戳 |
| updatedAt | Date | - | 授权最后更新的时间戳 |
选项
allowDynamicClientRegistration: boolean - 启用或禁用动态客户端注册。
metadata: OIDCMetadata - 自定义 OIDC 提供者元数据。
loginPage: string - 自定义登录页面的路径。
consentPage: string - 自定义授权页面的路径。
trustedClients: (Client & { skipConsent?: boolean })[] - 直接在提供者选项中配置的可信客户端数组。这些客户端绕过数据库查询,并可选择跳过授权页面。
getAdditionalUserInfoClaim: (user: User, scopes: string[], client: Client) => Record<string, any> - 用于获取额外用户信息声明(claims)的函数。
useJWTPlugin: boolean - 当设为 true 时,ID 令牌将使用 JWT 插件的非对称密钥进行签名。当设为 false(默认值)时,ID 令牌将使用应用密钥通过 HMAC-SHA256 进行签名。
schema: AuthPluginSchema - 自定义 OIDC 提供者的架构。