OIDC 提供者
此插件将很快被弃用,建议使用 OAuth 提供者插件。
OIDC 提供者插件 使你能够构建和管理自己的 OpenID Connect (OIDC) 提供者,从而完全控制用户身份验证,而无需依赖像 Okta 或 Azure AD 这样的第三方服务。它还允许其他服务通过你的 OIDC 提供者对用户进行身份验证。
【The OIDC Provider Plugin enables you to build and manage your own OpenID Connect (OIDC) provider, granting full control over user authentication without relying on third-party services like Okta or Azure AD. It also allows other services to authenticate users through your OIDC provider.】
主要特性:
- 客户端注册:注册客户端以便使用你的 OIDC 提供程序进行身份验证。
- 动态客户端注册:允许客户端动态注册。
- 受信任的客户端:配置具有可选同意绕过的硬编码受信任客户端。
- 授权码流程:支持授权码流程。
- 公共客户端:支持用于单页应用(SPA)、移动应用、命令行工具等的公共客户端。
- JWKS 端点:发布一个 JWKS 端点以允许客户端验证令牌。(尚未完全实现)
- 刷新令牌:颁发刷新令牌,并使用
refresh_token授权处理访问令牌的续期。 - OAuth 同意:为用户授权实现 OAuth 同意屏幕,并为受信任的应用提供绕过同意的选项。
- UserInfo 端点:为客户端提供一个 UserInfo 端点以检索用户详细信息。
该插件正在积极开发中,可能不适合在生产环境中使用。如有任何问题或漏洞,请在 GitHub上报告。
安装
【Installation】
安装插件
将 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 generateSee the Schema section to add the fields manually.
添加客户端插件
将 OIDC 客户端插件添加到你的认证客户端配置中。
import { createAuthClient } from "better-auth/client";
import { oidcClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [oidcClient({
// Your OIDC configuration
})]
})用法
【Usage】
安装完成后,你可以使用 OIDC 提供程序来管理应用中的身份验证流程。
【Once installed, you can utilize the OIDC Provider to manage authentication flows within your application.】
注册新客户
【Register a New Client】
要注册一个新的 OIDC 客户端,可以在客户端使用 oauth2.register 方法,或在服务器上使用 auth.api.registerOAuthApplication。
【To register a new OIDC client, use the oauth2.register method on the client or auth.api.registerOAuthApplication on the server.】
默认情况下,客户端注册需要身份验证。设置 allowDynamicClientRegistration: true 可允许公开注册。请确保在你的认证客户端配置中添加 oidcClient() 插件。
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,可以将其显示给用户。
【Once the application is created, you will receive a client_id and client_secret that you can display to the user.】
信任的客户
【Trusted Clients】
对于第一方应用和内部服务,你可以直接在 OIDC 提供商配置中配置受信任的客户端。受信任的客户端可以绕过数据库查询以提高性能,并且可以选择跳过同意屏幕以改善用户体验。
【For first-party applications and internal services, you can configure trusted clients directly in your OIDC provider configuration. Trusted clients bypass database lookups for better performance and can optionally skip consent screens for improved user experience.】
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 Endpoint】
OIDC 提供商包含一个 UserInfo 端点,允许客户端检索关于已认证用户的信息。此端点位于 /oauth2/userinfo,需要有效的访问令牌。
【The OIDC Provider includes a UserInfo endpoint that allows clients to retrieve information about the authenticated user. This endpoint is available at /oauth2/userinfo and requires a valid access token.】
服务器端使用
【Server-Side Usage】
import { auth } from "@/lib/auth";
const userInfo = await auth.api.oAuth2userInfo({
headers: {
authorization: "Bearer ACCESS_TOKEN"
}
});
// userInfo contains user details based on the scopes granted客户端使用(针对第三方 OAuth 客户端)
【Client-Side Usage (For Third-Party OAuth Clients)】
第三方 OAuth 客户端可以使用标准 HTTP 请求调用 UserInfo 端点:
【Third-party OAuth clients can call the UserInfo endpoint using standard HTTP requests:】
const response = await fetch('https://your-domain.com/api/auth/oauth2/userinfo', {
headers: {
'Authorization': 'Bearer ACCESS_TOKEN'
}
});
const userInfo = await response.json();基于范围的返回声明:
- 使用
openid权限:返回用户的 ID(sub声明) - 使用
profile范围:返回name、picture、given_name、family_name - 使用
email权限:返回email和email_verified
自定义声明
【Custom Claims】
getAdditionalUserInfoClaim 函数接收用户对象、请求的权限范围数组以及客户端,允许你根据授权时授予的权限范围有条件地包含声明。这些附加声明将包含在 UserInfo 端点响应和 ID 令牌中。
【The getAdditionalUserInfoClaim function receives the user object, requested scopes array, and the client, allowing you to conditionally include claims based on the scopes granted during authorization. These additional claims will be included in both the UserInfo endpoint response and the ID token.】
import { betterAuth } from "better-auth";
import { oidcProvider } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
oidcProvider({
loginPage: "/sign-in",
getAdditionalUserInfoClaim: async (user, scopes, client) => {
const claims: Record<string, any> = {};
// Add custom claims based on scopes
if (scopes.includes("profile")) {
claims.department = user.department;
claims.job_title = user.jobTitle;
}
// Add claims based on client metadata
if (client.metadata?.includeRoles) {
claims.roles = user.roles;
}
return claims;
}
})
]
});同意屏幕
【Consent Screen】
当用户被重定向到 OIDC 提供商进行身份验证时,可能会被提示授权应用访问他们的数据。这被称为同意屏幕。默认情况下,Better Auth 会显示一个示例同意屏幕。你可以在初始化时通过提供 consentPage 选项来自定义同意屏幕。
【When a user is redirected to the OIDC provider for authentication, they may be prompted to authorize the application to access their data. This is known as the consent screen. By default, Better Auth will display a sample consent screen. You can customize the consent screen by providing a consentPage option during initialization.】
注意:具有 skipConsent: true 的受信任客户端将完全跳过同意屏幕,为第一方应用提供无缝体验。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
plugins: [oidcProvider({
consentPage: "/path/to/consent/page"
})]
})该插件将把用户重定向到指定路径,并附带 consent_code、client_id 和 scope 查询参数。你可以使用这些信息来显示自定义的同意页面。一旦用户同意,你可以调用 oauth2.consent 来完成授权。
【The plugin will redirect the user to the specified path with consent_code, client_id and scope query parameters. You can use this information to display a custom consent screen. Once the user consents, you can call oauth2.consent to complete the authorization.】
同意端点支持两种传递同意码的方法:
【The consent endpoint supports two methods for passing the consent code:】
方法 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,
});方法二:基于 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 应用提供了更简单的实现方式。
【Both methods are fully supported. The URL parameter method works well with mobile apps and third-party contexts, while the cookie-based method provides a simpler implementation for web applications.】
登录处理
【Handling Login】
当用户被重定向到 OIDC 提供商进行身份验证时,如果他们尚未登录,将会被重定向到登录页面。你可以在初始化时通过提供 loginPage 选项来自定义登录页面。
【When a user is redirected to the OIDC provider for authentication, if they are not already logged in, they will be redirected to the login page. You can customize the login page by providing a loginPage option during initialization.】
import { betterAuth } from "better-auth";
export const auth = betterAuth({
plugins: [oidcProvider({
loginPage: "/sign-in"
})]
})你无需从你的端处理任何事情;当创建新会话时,插件将负责继续授权流程。
【You don't need to handle anything from your side; when a new session is created, the plugin will handle continuing the authorization flow.】
配置
【Configuration】
OIDC 元数据
【OIDC Metadata】
在初始化时通过提供配置对象来自定义 OIDC 元数据。
【Customize the OIDC metadata by providing a configuration object during initialization.】
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 端点
【JWKS Endpoint】
OIDC 提供程序插件可以与 JWT 插件集成,为 ID 令牌提供可在 JWKS 端点验证的非对称密钥签名。
【The OIDC Provider plugin can integrate with the JWT plugin to provide asymmetric key signing for ID tokens verifiable at a JWKS endpoint.】
为了使你的插件符合 OIDC,你必须禁用 /token 端点,OAuth 的等效端点位于 /oauth2/token。
【To make your plugin OIDC compliant, you MUST disable the /token endpoint, the OAuth equivalent is located at /oauth2/token instead.】
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 令牌使用应用密钥进行签名。
动态客户端注册
【Dynamic Client Registration】
如果你想允许客户端动态注册,可以通过将 allowDynamicClientRegistration 选项设置为 true 来启用此功能。
【If you want to allow clients to register dynamically, you can enable this feature by setting the allowDynamicClientRegistration option to true.】
const auth = betterAuth({
plugins: [oidcProvider({
allowDynamicClientRegistration: true,
})]
})这将允许客户端使用 /register 端点进行注册,并且该端点将公开可用。
【This will allow clients to register using the /register endpoint to be publicly available.】
架构
【Schema】
OIDC 提供者插件向数据库添加以下表:
【The OIDC Provider plugin adds the following tables to the database:】
OAuth 应用
【OAuth Application】
表名:oauthApplication
【Table Name: oauthApplication】
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | Database ID of the OAuth client | |
| clientId | string | Unique identifier for each OAuth client | |
| clientSecret | string | Secret key for the OAuth client. Optional for public clients using PKCE. | |
| name | string | - | Name of the OAuth client |
| redirectUrls | string | - | Comma-separated list of redirect URLs |
| metadata | string | Additional metadata for the OAuth client | |
| type | string | - | Type of OAuth client (e.g., web, mobile) |
| disabled | boolean | - | Indicates if the client is disabled |
| userId | string | ID of the user who owns the client. (optional) | |
| createdAt | Date | - | Timestamp of when the OAuth client was created |
| updatedAt | Date | - | Timestamp of when the OAuth client was last updated |
OAuth 访问令牌
【OAuth Access Token】
表名:oauthAccessToken
【Table Name: oauthAccessToken】
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | Database ID of the access token | |
| accessToken | string | - | Access token issued to the client |
| refreshToken | string | - | Refresh token issued to the client |
| accessTokenExpiresAt | Date | - | Expiration date of the access token |
| refreshTokenExpiresAt | Date | - | Expiration date of the refresh token |
| clientId | string | ID of the OAuth client | |
| userId | string | ID of the user associated with the token | |
| scopes | string | - | Comma-separated list of scopes granted |
| createdAt | Date | - | Timestamp of when the access token was created |
| updatedAt | Date | - | Timestamp of when the access token was last updated |
OAuth 同意
【OAuth Consent】
表名:oauthConsent
【Table Name: oauthConsent】
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | Database ID of the consent | |
| userId | string | ID of the user who gave consent | |
| clientId | string | ID of the OAuth client | |
| scopes | string | - | Comma-separated list of scopes consented to |
| consentGiven | boolean | - | Indicates if consent was given |
| createdAt | Date | - | Timestamp of when the consent was given |
| updatedAt | Date | - | Timestamp of when the consent was last updated |
选项
【Options】
allowDynamicClientRegistration:boolean - 启用或禁用动态客户端注册。
元数据: 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 提供程序的模式。