OIDC 提供程序
OIDC 提供程序插件 使您能够构建和管理自己的 OpenID Connect (OIDC) 提供程序,从而在不依赖 Okta 或 Azure AD 等第三方服务的情况下,对用户身份验证获得完全控制。它还允许其他服务通过您的 OIDC 提供程序验证用户。
关键特性:
- 客户端注册: 注册客户端以使用您的 OIDC 提供程序进行身份验证。
- 动态客户端注册: 允许客户端动态注册。
- 受信任客户端: 配置硬编码的受信任客户端,可选择绕过同意。
- 授权码流程: 支持授权码流程。
- 公共客户端: 支持 SPA、移动应用、CLI 工具等的公共客户端。
- JWKS 端点: 发布 JWKS 端点,允许客户端验证令牌。(尚未完全实现)
- 刷新令牌: 颁发刷新令牌,并使用
refresh_token授权处理访问令牌续期。 - OAuth 同意: 为用户授权实现 OAuth 同意屏幕,并为受信任应用程序提供绕过同意的选项。
- UserInfo 端点: 为客户端提供 UserInfo 端点,以检索用户详细信息。
此插件处于积极开发中,可能不适合生产环境使用。请在 GitHub 上报告任何问题或错误。
安装
挂载插件
将 OIDC 插件添加到您的 auth 配置中。请参阅 OIDC 配置 以了解如何配置插件。
import { betterAuth } from "better-auth";
import { oidcProvider } from "better-auth/plugins";
const auth = betterAuth({
plugins: [oidcProvider({
loginPage: "/sign-in", // path to the login page
// ...other options
})]
})迁移数据库
运行迁移或生成架构,以向数据库添加必要的字段和表。
npx @better-auth/cli migratenpx @better-auth/cli generate请参阅 架构 部分以手动添加字段。
添加客户端插件
将 OIDC 客户端插件添加到您的 auth 客户端配置中。
import { createAuthClient } from "better-auth/client";
import { oidcClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [oidcClient({
// Your OIDC configuration
})]
})使用
安装完成后,您可以在应用程序中使用 OIDC 提供程序来管理身份验证流程。
注册新客户端
要注册新的 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,});| Prop | Description | Type |
|---|---|---|
redirect_uris | A list of redirect URIs. | string[] |
token_endpoint_auth_method? | The authentication method for the token endpoint. | "none" | "client_secret_basic" | "client_secret_post" |
grant_types? | The grant types supported by the application. | ("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? | The response types supported by the application. | ("code" | "token")[] |
client_name? | The name of the application. | string |
client_uri? | The URI of the application. | string |
logo_uri? | The URI of the application logo. | string |
scope? | The scopes supported by the application. Separated by spaces. | string |
contacts? | The contact information for the application. | string[] |
tos_uri? | The URI of the application terms of service. | string |
policy_uri? | The URI of the application privacy policy. | string |
jwks_uri? | The URI of the application JWKS. | string |
jwks? | The JWKS of the application. | Record<string, any> |
metadata? | The metadata of the application. | Record<string, any> |
software_id? | The software ID of the application. | string |
software_version? | The software version of the application. | string |
software_statement? | The software statement of the application. | string |
此端点支持符合 RFC7591 的客户端注册。
创建应用程序后,您将收到 client_id 和 client_secret,您可以将它们显示给用户。
受信任客户端
对于第一方应用程序和内部服务,您可以直接在 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, // Skip consent for this trusted client
metadata: { internal: true }
},
{
clientId: "mobile-app",
clientSecret: "mobile-secret",
name: "Company Mobile App",
type: "native",
redirectURLs: ["com.company.app://auth"],
disabled: false,
skipConsent: false, // Still require consent if needed
metadata: {}
}
]
})]
})UserInfo 端点
OIDC 提供程序包含一个 UserInfo 端点,允许客户端检索已验证用户的信息。此端点位于 /oauth2/userinfo,需要有效的访问令牌。
// Example of how a client would use the UserInfo endpoint
const response = await fetch('https://your-domain.com/api/auth/oauth2/userinfo', {
headers: {
'Authorization': 'Bearer ACCESS_TOKEN'
}
});
const userInfo = await response.json();
// userInfo contains user details based on the scopes grantedUserInfo 端点根据授权期间授予的范围返回不同的声明:
- 使用
openid范围:返回用户的 ID (sub声明) - 使用
profile范围:返回 name、picture、given_name、family_name - 使用
email范围:返回 email 和 email_verified
getAdditionalUserInfoClaim 函数接收用户对象、请求的范围数组和客户端,允许您根据授权期间授予的范围有条件地包含声明。这些附加声明将包含在 UserInfo 端点响应和 ID 令牌中。
同意屏幕
当用户被重定向到 OIDC 提供程序进行身份验证时,如果他们尚未登录,他们将被重定向到登录页面。您可以通过在初始化时提供 loginPage 选项来自定义登录页面。如果他们尚未登录,他们将被重定向到登录页面。您可以通过在初始化时提供 loginPage 选项来自定义登录页面。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
plugins: [oidcProvider({
consentPage: "/path/to/consent/page"
})]
})插件将使用 consent_code、client_id 和 scope 查询参数将用户重定向到指定路径。您可以使用此信息显示自定义同意屏幕。一旦用户同意,您可以调用 oauth2.consent 来完成授权。
同意端点支持两种传递同意代码的方法:
方法 1: URL 参数
// Get the consent code from the URL
const params = new URLSearchParams(window.location.search);
// Submit consent with the code in the request body
const consentCode = params.get('consent_code');
if (!consentCode) {
throw new Error('Consent code not found in URL parameters');
}
const res = await client.oauth2.consent({
accept: true, // or false to deny
consent_code: consentCode,
});方法 2: 基于 Cookie
// The consent code is automatically stored in a signed cookie
// Just submit the consent decision
const res = await client.oauth2.consent({
accept: true, // or false to deny
// consent_code not needed when using cookie-based flow
});两种方法均得到完全支持。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",
// ...other custom metadata
}
})]
})JWKS 端点
OIDC 提供程序插件可以与 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(), // Make sure to add the JWT plugin
oidcProvider({
useJWTPlugin: true, // Enable JWT plugin integration
loginPage: "/sign-in",
// ... other options
})
]
})当 useJWTPlugin: false (默认) 时,ID 令牌使用应用程序密钥进行签名。
动态客户端注册
如果您想允许客户端动态注册,您可以通过将 allowDynamicClientRegistration 选项设置为 true 来启用此功能。
const auth = betterAuth({
plugins: [oidcProvider({
allowDynamicClientRegistration: true,
})]
})这将允许客户端使用 /register 端点进行注册,使其公开可用。
架构
OIDC 提供程序插件向数据库添加以下表:
OAuth 应用程序
表名: oauthApplication
| Field Name | Type | Key | Description |
|---|---|---|---|
| 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
| Field Name | Type | Key | Description |
|---|---|---|---|
| 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
| Field Name | Type | Key | Description |
|---|---|---|---|
| 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> - 获取附加用户信息的声明的函数。
useJWTPlugin: boolean - 当为 true 时,ID 令牌使用 JWT 插件的非对称密钥签名。当为 false (默认) 时,ID 令牌使用应用程序密钥的 HMAC-SHA256 签名。
schema: AuthPluginSchema - 自定义 OIDC 提供程序架构。