OIDC Provider
OIDC Provider 플러그인을 사용하면 OpenID Connect (OIDC) provider를 구축하고 관리할 수 있으며, Okta나 Azure AD와 같은 타사 서비스에 의존하지 않고 사용자 인증을 완벽하게 제어할 수 있습니다. 또한 다른 서비스가 귀하의 OIDC provider를 통해 사용자를 인증할 수 있도록 허용합니다.
주요 기능:
- 클라이언트 등록: OIDC provider로 인증할 클라이언트를 등록합니다.
- 동적 클라이언트 등록: 클라이언트가 동적으로 등록할 수 있도록 허용합니다.
- 신뢰할 수 있는 클라이언트: 선택적 동의 우회 옵션과 함께 하드코딩된 신뢰할 수 있는 클라이언트를 구성합니다.
- Authorization Code Flow: Authorization Code Flow를 지원합니다.
- Public 클라이언트: SPA, 모바일 앱, CLI 도구 등을 위한 public 클라이언트를 지원합니다.
- JWKS Endpoint: 클라이언트가 토큰을 검증할 수 있도록 JWKS endpoint를 게시합니다. (완전히 구현되지 않음)
- Refresh Token: refresh token을 발급하고
refresh_tokengrant를 사용하여 access token 갱신을 처리합니다. - OAuth Consent: 사용자 인증을 위한 OAuth 동의 화면을 구현하고, 신뢰할 수 있는 애플리케이션의 경우 동의를 우회할 수 있는 옵션을 제공합니다.
- UserInfo Endpoint: 클라이언트가 사용자 세부 정보를 검색할 수 있는 UserInfo endpoint를 제공합니다.
이 플러그인은 활발히 개발 중이며 프로덕션 환경에 적합하지 않을 수 있습니다. 문제나 버그는 GitHub에 보고해 주세요.
설치
플러그인 마운트
auth 설정에 OIDC 플러그인을 추가합니다. 플러그인 구성 방법은 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 섹션을 참조하세요.
클라이언트 플러그인 추가
auth 클라이언트 설정에 OIDC 클라이언트 플러그인을 추가합니다.
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,});| Prop | Description | Type |
|---|---|---|
redirect_uris | 리디렉션 URI 목록입니다. | string[] |
token_endpoint_auth_method? | 토큰 endpoint의 인증 메서드입니다. | "none" | "client_secret_basic" | "client_secret_post" |
grant_types? | 애플리케이션이 지원하는 grant 타입입니다. | ("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? | 애플리케이션이 지원하는 response 타입입니다. | ("code" | "token")[] |
client_name? | 애플리케이션의 이름입니다. | string |
client_uri? | 애플리케이션의 URI입니다. | string |
logo_uri? | 애플리케이션 로고의 URI입니다. | string |
scope? | 애플리케이션이 지원하는 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? | 애플리케이션의 소프트웨어 statement입니다. | string |
이 endpoint는 RFC7591 호환 클라이언트 등록을 지원합니다.
애플리케이션이 생성되면 사용자에게 표시할 수 있는 client_id와 client_secret을 받게 됩니다.
신뢰할 수 있는 클라이언트
자사 애플리케이션 및 내부 서비스의 경우, OIDC provider 구성에서 직접 신뢰할 수 있는 클라이언트를 구성할 수 있습니다. 신뢰할 수 있는 클라이언트는 더 나은 성능을 위해 데이터베이스 조회를 우회하며 향상된 사용자 경험을 위해 선택적으로 동의 화면을 건너뛸 수 있습니다.
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: {}
}
]
})]
})UserInfo Endpoint
OIDC Provider에는 클라이언트가 인증된 사용자에 대한 정보를 검색할 수 있는 UserInfo endpoint가 포함되어 있습니다. 이 endpoint는 /oauth2/userinfo에서 사용할 수 있으며 유효한 access token이 필요합니다.
// 클라이언트가 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에는 부여된 scope에 따른 사용자 세부 정보가 포함됩니다UserInfo endpoint는 인증 중에 부여된 scope에 따라 다른 claim을 반환합니다:
openidscope 포함: 사용자 ID(subclaim) 반환profilescope 포함: name, picture, given_name, family_name 반환emailscope 포함: email 및 email_verified 반환
getAdditionalUserInfoClaim 함수는 사용자 객체, 요청된 scope 배열 및 클라이언트를 받아, 인증 중에 부여된 scope에 따라 조건부로 claim을 포함할 수 있습니다. 이러한 추가 claim은 UserInfo endpoint 응답과 ID token 모두에 포함됩니다.
동의 화면
사용자가 인증을 위해 OIDC provider로 리디렉션되면 애플리케이션이 데이터에 액세스하도록 권한을 부여하라는 메시지가 표시될 수 있습니다. 이것을 동의 화면이라고 합니다. 기본적으로 Better Auth는 샘플 동의 화면을 표시합니다. 초기화 중에 consentPage 옵션을 제공하여 동의 화면을 사용자 정의할 수 있습니다.
참고: 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를 호출하여 인증을 완료할 수 있습니다.
동의 endpoint는 동의 코드를 전달하는 두 가지 방법을 지원합니다:
방법 1: 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,
});방법 2: 쿠키 기반
// 동의 코드는 서명된 쿠키에 자동으로 저장됩니다
// 동의 결정만 제출하면 됩니다
const res = await client.oauth2.consent({
accept: true, // 또는 거부하려면 false
// 쿠키 기반 플로우를 사용할 때는 consent_code가 필요하지 않습니다
});두 방법 모두 완전히 지원됩니다. URL 매개변수 방법은 모바일 앱 및 타사 컨텍스트에서 잘 작동하며, 쿠키 기반 방법은 웹 애플리케이션에 더 간단한 구현을 제공합니다.
로그인 처리
사용자가 인증을 위해 OIDC provider로 리디렉션되었을 때 이미 로그인하지 않은 경우 로그인 페이지로 리디렉션됩니다. 초기화 중에 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 Endpoint
OIDC Provider 플러그인은 JWT 플러그인과 통합하여 JWKS endpoint에서 검증 가능한 ID token을 위한 비대칭 키 서명을 제공할 수 있습니다.
플러그인을 OIDC 호환으로 만들려면 /token endpoint를 반드시 비활성화해야 합니다. OAuth에 해당하는 endpoint는 /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 token은 애플리케이션 secret으로 서명됩니다.
동적 클라이언트 등록
클라이언트가 동적으로 등록할 수 있도록 하려면 allowDynamicClientRegistration 옵션을 true로 설정하여 이 기능을 활성화할 수 있습니다.
const auth = betterAuth({
plugins: [oidcProvider({
allowDynamicClientRegistration: true,
})]
})이렇게 하면 클라이언트가 /register endpoint를 사용하여 공개적으로 등록할 수 있습니다.
Schema
OIDC Provider 플러그인은 다음 테이블을 데이터베이스에 추가합니다:
OAuth Application
테이블 이름: oauthApplication
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | OAuth 클라이언트의 데이터베이스 ID | |
| clientId | string | 각 OAuth 클라이언트의 고유 식별자 | |
| clientSecret | string | OAuth 클라이언트의 비밀 키. PKCE를 사용하는 public 클라이언트의 경우 선택 사항입니다. | |
| 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 Access Token
테이블 이름: oauthAccessToken
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | access token의 데이터베이스 ID | |
| accessToken | string | - | 클라이언트에게 발급된 access token |
| refreshToken | string | - | 클라이언트에게 발급된 refresh token |
| accessTokenExpiresAt | Date | - | access token의 만료 날짜 |
| refreshTokenExpiresAt | Date | - | refresh token의 만료 날짜 |
| clientId | string | OAuth 클라이언트의 ID | |
| userId | string | 토큰과 연결된 사용자의 ID | |
| scopes | string | - | 부여된 scope의 쉼표로 구분된 목록 |
| createdAt | Date | - | access token이 생성된 시간의 타임스탬프 |
| updatedAt | Date | - | access token이 마지막으로 업데이트된 시간의 타임스탬프 |
OAuth Consent
테이블 이름: oauthConsent
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | 동의의 데이터베이스 ID | |
| userId | string | 동의한 사용자의 ID | |
| clientId | string | OAuth 클라이언트의 ID | |
| scopes | string | - | 동의한 scope의 쉼표로 구분된 목록 |
| consentGiven | boolean | - | 동의가 부여되었는지 여부를 나타냅니다 |
| createdAt | Date | - | 동의가 부여된 시간의 타임스탬프 |
| updatedAt | Date | - | 동의가 마지막으로 업데이트된 시간의 타임스탬프 |
옵션
allowDynamicClientRegistration: boolean - 동적 클라이언트 등록을 활성화하거나 비활성화합니다.
metadata: OIDCMetadata - OIDC provider 메타데이터를 사용자 정의합니다.
loginPage: string - 사용자 정의 로그인 페이지의 경로입니다.
consentPage: string - 사용자 정의 동의 페이지의 경로입니다.
trustedClients: (Client & { skipConsent?: boolean })[] - provider 옵션에서 직접 구성되는 신뢰할 수 있는 클라이언트의 배열입니다. 이러한 클라이언트는 데이터베이스 조회를 우회하며 선택적으로 동의 화면을 건너뛸 수 있습니다.
getAdditionalUserInfoClaim: (user: User, scopes: string[], client: Client) => Record<string, any> - 추가 사용자 정보 claim을 가져오는 함수입니다.
useJWTPlugin: boolean - true일 때 ID token은 JWT 플러그인의 비대칭 키를 사용하여 서명됩니다. false(기본값)일 때 ID token은 애플리케이션 secret을 사용하여 HMAC-SHA256으로 서명됩니다.
schema: AuthPluginSchema - OIDC provider schema를 사용자 정의합니다.