OIDC 提供者
Better Auth 的 Open ID Connect 插件,允许您拥有自己的 OIDC 提供者。
此插件即将被弃用,将由 OAuth Provider Plugin 取代。
OIDC 提供者插件 使您能够构建并管理自己的 OpenID Connect (OIDC) 提供者,全面掌控用户认证,无需依赖第三方服务如 Okta 或 Azure AD。它还允许其他服务通过您的 OIDC 提供者进行用户认证。
主要功能:
- 客户端注册:注册客户端以使用您的 OIDC 提供者进行认证。
- 动态客户端注册:允许客户端动态注册。
- 受信任客户端:配置硬编码的受信任客户端,支持可选的跳过用户同意。
- 授权码流程:支持授权码流程。
- 公共客户端:支持 SPA、移动应用、CLI 工具等公共客户端。
- JWKS 端点:发布 JWKS 端点供客户端验证令牌。(尚未完全实现)
- 刷新令牌:签发刷新令牌并使用
refresh_token授权刷新访问令牌。 - OAuth 同意:实现 OAuth 同意页面供用户授权,受信任应用可选择跳过同意。
- UserInfo 端点:提供 UserInfo 端点,客户端可获取用户详情。
此插件正在积极开发中,可能不适合生产环境使用。如发现问题或 Bug,请在 GitHub 报告。
安装
挂载插件
将 OIDC 插件添加到您的 auth 配置中。查看配置部分了解插件的配置方法。
import { betterAuth } from "better-auth";
import { oidcProvider } from "better-auth/plugins";
const auth = betterAuth({
plugins: [
oidcProvider({
loginPage: "/sign-in", // 登录页面路径
// ...其他选项
})
]
})添加客户端插件
将 OIDC 客户端插件添加到您的 auth 客户端配置中。
import { createAuthClient } from "better-auth/client";
import { oidcClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [
oidcClient({
// 您的 OIDC 配置
})
]
})使用
安装完成后,您可以利用 OIDC 提供者来管理应用内的认证流程。
注册新客户端
要注册新的 OIDC 客户端,可以在客户端调用 oauth2.register 方法,或在服务器调用 auth.api.registerOAuthApplication 接口。
默认情况下,客户端注册需认证。设置 allowDynamicClientRegistration: true 可允许公开注册。确保在 Auth 客户端配置中添加 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,});redirect_urisstring[]required重定向 URI 列表。
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_namestring应用名称。
client_uristring应用 URI。
logo_uristring应用图标 URI。
scopestring应用支持的作用域,空格分隔。
contactsstring[]应用联系方式。
tos_uristring应用服务条款 URI。
policy_uristring应用隐私政策 URI。
jwks_uristring应用 JWKS URI。
jwksRecord<string, any>应用 JWKS 数据。
metadataRecord<string, any>应用元数据。
software_idstring应用软件 ID。
software_versionstring应用软件版本。
software_statementstring应用软件声明。
此接口支持符合 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: "内部管理后台",
type: "web",
redirectUrls: ["https://dashboard.company.com/auth/callback"],
disabled: false,
skipConsent: true, // 此受信任客户端跳过同意页面
metadata: { internal: true }
},
{
clientId: "mobile-app",
clientSecret: "mobile-secret",
name: "公司移动应用",
type: "native",
redirectUrls: ["com.company.app://auth"],
disabled: false,
skipConsent: false, // 需要同意时仍显示同意页面
metadata: {}
}
]
})]
})UserInfo 端点
OIDC 提供者包含 UserInfo 端点,允许客户端获取认证用户的信息。该端点路径为 /oauth2/userinfo,需提供有效的访问令牌。
服务器端用法
import { auth } from "@/lib/auth";
const userInfo = await auth.api.oAuth2userInfo({
headers: {
authorization: "Bearer ACCESS_TOKEN"
}
});
// userInfo 中包含基于授权作用域的用户详情客户端用法(针对第三方 OAuth 客户端)
第三方 OAuth 客户端可通过标准 HTTP 请求调用 UserInfo 端点:
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
自定义声明
getAdditionalUserInfoClaim 函数接收用户对象、请求的作用域数组以及客户端,可以基于授权时赋予的作用域有条件地加入自定义声明,这些额外声明将包含在 UserInfo 端点响应和 ID 令牌中。
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> = {};
// 根据作用域添加自定义声明
if (scopes.includes("profile")) {
claims.department = user.department;
claims.job_title = user.jobTitle;
}
// 基于客户端元数据添加声明
if (client.metadata?.includeRoles) {
claims.roles = user.roles;
}
return claims;
}
})
]
});同意页面
当用户被重定向到 OIDC 提供者进行认证时,可能会被提示授权应用访问其数据,即同意页面。默认情况下,Better Auth 会显示示例同意页面。你也可以通过初始化时传入 consentPage 选项来自定义同意页面。
注意:skipConsent: true 的受信任客户端将直接跳过同意页面,提供无缝体验。
import { betterAuth } from "better-auth";
import { oidcProvider } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
oidcProvider({
consentPage: "/path/to/consent/page"
})
]
})插件会将用户重定向到指定路径,并附带 consent_code、client_id 和 scope 查询参数。您可以使用这些信息显示自定义同意页面。用户同意后,可以调用 oauth2.consent 完成授权。
同意端点支持两种传递同意码的方法:
方法一:URL 参数
import { authClient } from "@/lib/auth-client"
// 从 URL 获取同意码
const params = new URLSearchParams(window.location.search);
// 在请求体中提交同意码
const consentCode = params.get('consent_code');
if (!consentCode) {
throw new Error('URL 参数中未找到同意码');
}
const res = await authClient.oauth2.consent({
accept: true, // 或 false 拒绝
consent_code: consentCode,
});方法二:基于 Cookie
import { authClient } from "@/lib/auth-client"
// 同意码会自动存储在签名 Cookie 中
// 直接提交同意结果即可
const res = await authClient.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 提供者插件可以与 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 令牌使用应用密钥以 HMAC-SHA256 签名。
动态客户端注册
若允许客户端动态注册,可开启该功能,设置 allowDynamicClientRegistration 为 true。
const auth = betterAuth({
plugins: [
oidcProvider({
allowDynamicClientRegistration: true,
})
]
})此时,客户端可通过 /register 端点公开注册。
数据库结构
OIDC 提供者插件在数据库中添加以下表:
OAuth 应用
表名:oauthApplication
OAuth 访问令牌
表名:oauthAccessToken
OAuth 同意
表名:oauthConsent
选项
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 提供者的数据库结构。