OIDC 提供程序

OIDC 提供程序插件 使您能够构建和管理自己的 OpenID Connect (OIDC) 提供程序,从而在不依赖 Okta 或 Azure AD 等第三方服务的情况下,对用户身份验证获得完全控制。它还允许其他服务通过您的 OIDC 提供程序验证用户。

关键特性:

  • 客户端注册: 注册客户端以使用您的 OIDC 提供程序进行身份验证。
  • 动态客户端注册: 允许客户端动态注册。
  • 受信任客户端: 配置硬编码的受信任客户端,可选择绕过同意。
  • 授权码流程: 支持授权码流程。
  • 公共客户端: 支持 SPA、移动应用、CLI 工具等的公共客户端。
  • JWKS 端点: 发布 JWKS 端点,允许客户端验证令牌。(尚未完全实现)
  • 刷新令牌: 颁发刷新令牌,并使用 refresh_token 授权处理访问令牌续期。
  • OAuth 同意: 为用户授权实现 OAuth 同意屏幕,并为受信任应用程序提供绕过同意的选项。
  • UserInfo 端点: 为客户端提供 UserInfo 端点,以检索用户详细信息。

此插件处于积极开发中,可能不适合生产环境使用。请在 GitHub 上报告任何问题或错误。

安装

挂载插件

将 OIDC 插件添加到您的 auth 配置中。请参阅 OIDC 配置 以了解如何配置插件。

auth.ts
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 migrate
npx @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"],
});

完整方法

POST
/oauth2/register
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,});
PropDescriptionType
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_idclient_secret,您可以将它们显示给用户。

受信任客户端

对于第一方应用程序和内部服务,您可以直接在 OIDC 提供程序配置中配置受信任客户端。受信任客户端绕过数据库查找以获得更好的性能,并可以选择跳过同意屏幕以改善用户体验。

auth.ts
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,需要有效的访问令牌。

GET
/oauth2/userinfo
client-app.ts
// 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 granted

UserInfo 端点根据授权期间授予的范围返回不同的声明:

  • 使用 openid 范围:返回用户的 ID (sub 声明)
  • 使用 profile 范围:返回 name、picture、given_name、family_name
  • 使用 email 范围:返回 email 和 email_verified

getAdditionalUserInfoClaim 函数接收用户对象、请求的范围数组和客户端,允许您根据授权期间授予的范围有条件地包含声明。这些附加声明将包含在 UserInfo 端点响应和 ID 令牌中。

同意屏幕

当用户被重定向到 OIDC 提供程序进行身份验证时,如果他们尚未登录,他们将被重定向到登录页面。您可以通过在初始化时提供 loginPage 选项来自定义登录页面。如果他们尚未登录,他们将被重定向到登录页面。您可以通过在初始化时提供 loginPage 选项来自定义登录页面。

auth.ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
    plugins: [oidcProvider({
        consentPage: "/path/to/consent/page"
    })]
})

插件将使用 consent_codeclient_idscope 查询参数将用户重定向到指定路径。您可以使用此信息显示自定义同意屏幕。一旦用户同意,您可以调用 oauth2.consent 来完成授权。

POST
/oauth2/consent

同意端点支持两种传递同意代码的方法:

方法 1: URL 参数

consent-page.ts
// 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

consent-page.ts
// 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 选项来自定义登录页面。

auth.ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
    plugins: [oidcProvider({
        loginPage: "/sign-in"
    })]
})

您无需从您的侧处理任何内容;当新会话创建时,插件将处理继续授权流程。

配置

OIDC 元数据

通过在初始化时提供配置对象来自定义 OIDC 元数据。

auth.ts
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

auth.ts
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 来启用此功能。

auth.ts
const auth = betterAuth({
    plugins: [oidcProvider({
        allowDynamicClientRegistration: true,
    })]
})

这将允许客户端使用 /register 端点进行注册,使其公开可用。

架构

OIDC 提供程序插件向数据库添加以下表:

OAuth 应用程序

表名: oauthApplication

Field NameTypeKeyDescription
idstringOAuth 客户端的数据库 ID
clientIdstring每个 OAuth 客户端的唯一标识符
clientSecretstringOAuth 客户端的密钥。对于使用 PKCE 的公共客户端,此项可选。
namestring-OAuth 客户端的名称
redirectURLsstring-逗号分隔的重定向 URL 列表
metadatastringOAuth 客户端的附加元数据
typestring-OAuth 客户端类型(例如,web、mobile)
disabledboolean-指示客户端是否被禁用
userIdstring拥有客户端的用户的 ID。(可选)
createdAtDate-OAuth 客户端创建时的时间戳
updatedAtDate-OAuth 客户端最后更新时的时间戳

OAuth 访问令牌

表名: oauthAccessToken

Field NameTypeKeyDescription
idstring访问令牌的数据库 ID
accessTokenstring-颁发给客户端的访问令牌
refreshTokenstring-颁发给客户端的刷新令牌
accessTokenExpiresAtDate-访问令牌的到期日期
refreshTokenExpiresAtDate-刷新令牌的到期日期
clientIdstringOAuth 客户端的 ID
userIdstring与令牌关联的用户的 ID
scopesstring-授予的范围的逗号分隔列表
createdAtDate-访问令牌创建时的时间戳
updatedAtDate-访问令牌最后更新时的时间戳

OAuth 同意

表名: oauthConsent

Field NameTypeKeyDescription
idstring同意的数据库 ID
userIdstring给予同意的用户的 ID
clientIdstringOAuth 客户端的 ID
scopesstring-同意的范围的逗号分隔列表
consentGivenboolean-指示是否给予了同意
createdAtDate-给予同意时的的时间戳
updatedAtDate-同意最后更新时的的时间戳

选项

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 提供程序架构。

On this page