configuring-better-auth
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBetter Auth OAuth/OIDC
Better Auth OAuth/OIDC
Implement centralized authentication with Better Auth - either as an auth server or SSO client.
使用Better Auth实现集中式认证——可作为认证服务器或SSO客户端。
MCP Server Setup
MCP服务器设置
Better Auth provides an MCP server powered by Chonkie for guided configuration:
bash
claude mcp add --transport http better-auth https://mcp.chonkie.ai/better-auth/better-auth-builder/mcpOr in :
settings.jsonjson
{
"mcpServers": {
"better-auth": {
"type": "http",
"url": "https://mcp.chonkie.ai/better-auth/better-auth-builder/mcp"
}
}
}Better Auth提供由Chonkie驱动的MCP服务器,用于引导式配置:
bash
claude mcp add --transport http better-auth https://mcp.chonkie.ai/better-auth/better-auth-builder/mcp或在中配置:
settings.jsonjson
{
"mcpServers": {
"better-auth": {
"type": "http",
"url": "https://mcp.chonkie.ai/better-auth/better-auth-builder/mcp"
}
}
}When to Use the MCP
何时使用MCP
| Task | Use MCP? |
|---|---|
| Initial Better Auth setup | Yes - guided configuration |
| Adding OIDC provider plugin | Yes - generates correct config |
| Troubleshooting auth issues | Yes - can analyze setup |
| Understanding auth flow | Yes - explains concepts |
| Writing custom middleware | No - use patterns below |
| 任务 | 是否使用MCP? |
|---|---|
| 初始Better Auth设置 | 是 - 引导式配置 |
| 添加OIDC提供商插件 | 是 - 生成正确配置 |
| 排查认证问题 | 是 - 可分析现有配置 |
| 理解认证流程 | 是 - 讲解相关概念 |
| 编写自定义中间件 | 否 - 使用下方的模式 |
Architecture Overview
架构概述
┌─────────────────┐
│ Better Auth SSO │ ← Central auth server (auth-server-setup.md)
│ (Auth Server) │
└────────┬────────┘
│
┌────┴────┐
▼ ▼
┌───────┐ ┌───────┐
│ App 1 │ │ App 2 │ ← SSO clients (sso-client-integration.md)
└───────┘ └───────┘┌─────────────────┐
│ Better Auth SSO │ ← 集中式认证服务器(auth-server-setup.md)
│ (Auth Server) │
└────────┬────────┘
│
┌────┴────┐
▼ ▼
┌───────┐ ┌───────┐
│ App 1 │ │ App 2 │ ← SSO客户端(sso-client-integration.md)
└───────┘ └───────┘Quick Start: Auth Server Setup
快速开始:认证服务器搭建
bash
npm install better-auth @better-auth/oidc-provider drizzle-ormbash
npm install better-auth @better-auth/oidc-provider drizzle-ormCore Configuration
核心配置
typescript
// src/lib/auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { oidcProvider } from "better-auth/plugins/oidc-provider";
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "pg", schema }),
emailAndPassword: { enabled: true },
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // 1 day
},
plugins: [
oidcProvider({
loginPage: "/sign-in",
consentPage: "/consent",
// PKCE for public clients (recommended)
requirePKCE: true,
}),
],
});typescript
// src/lib/auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { oidcProvider } from "better-auth/plugins/oidc-provider";
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "pg", schema }),
emailAndPassword: { enabled: true },
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // 1 day
},
plugins: [
oidcProvider({
loginPage: "/sign-in",
consentPage: "/consent",
// PKCE for public clients (recommended)
requirePKCE: true,
}),
],
});Register OAuth Clients
注册OAuth客户端
typescript
// Register SSO client
await auth.api.createOAuthClient({
name: "My App",
redirectUris: ["http://localhost:3000/api/auth/callback"],
type: "public", // Use 'public' for PKCE
});See references/auth-server-setup.md for complete setup with JWKS, email verification, and admin dashboard.
typescript
// Register SSO client
await auth.api.createOAuthClient({
name: "My App",
redirectUris: ["http://localhost:3000/api/auth/callback"],
type: "public", // Use 'public' for PKCE
});完整的搭建教程(包含JWKS、邮箱验证和管理控制台)请查看 references/auth-server-setup.md。
Quick Start: SSO Client Integration
快速开始:SSO客户端集成
bash
npm install josebash
npm install joseEnvironment Variables
环境变量
env
NEXT_PUBLIC_SSO_URL=http://localhost:3001
NEXT_PUBLIC_SSO_CLIENT_ID=your-client-idenv
NEXT_PUBLIC_SSO_URL=http://localhost:3001
NEXT_PUBLIC_SSO_CLIENT_ID=your-client-idPKCE Auth Flow
PKCE认证流程
typescript
// lib/auth-client.ts
import { generateCodeVerifier, generateCodeChallenge } from "./pkce";
export async function startLogin() {
const verifier = generateCodeVerifier();
const challenge = await generateCodeChallenge(verifier);
// Store verifier in cookie
document.cookie = `pkce_verifier=${verifier}; path=/; SameSite=Lax`;
const params = new URLSearchParams({
client_id: process.env.NEXT_PUBLIC_SSO_CLIENT_ID!,
redirect_uri: `${window.location.origin}/api/auth/callback`,
response_type: "code",
scope: "openid profile email",
code_challenge: challenge,
code_challenge_method: "S256",
});
window.location.href = `${SSO_URL}/oauth2/authorize?${params}`;
}typescript
// lib/auth-client.ts
import { generateCodeVerifier, generateCodeChallenge } from "./pkce";
export async function startLogin() {
const verifier = generateCodeVerifier();
const challenge = await generateCodeChallenge(verifier);
// Store verifier in cookie
document.cookie = `pkce_verifier=${verifier}; path=/; SameSite=Lax`;
const params = new URLSearchParams({
client_id: process.env.NEXT_PUBLIC_SSO_CLIENT_ID!,
redirect_uri: `${window.location.origin}/api/auth/callback`,
response_type: "code",
scope: "openid profile email",
code_challenge: challenge,
code_challenge_method: "S256",
});
window.location.href = `${SSO_URL}/oauth2/authorize?${params}`;
}Token Exchange (API Route)
令牌交换(API路由)
typescript
// app/api/auth/callback/route.ts
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const code = searchParams.get("code");
const verifier = cookies().get("pkce_verifier")?.value;
const response = await fetch(`${SSO_URL}/oauth2/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: process.env.NEXT_PUBLIC_SSO_CLIENT_ID!,
code: code!,
redirect_uri: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback`,
code_verifier: verifier!,
}),
});
const tokens = await response.json();
// Set httpOnly cookies
const res = NextResponse.redirect("/dashboard");
res.cookies.set("access_token", tokens.access_token, { httpOnly: true });
res.cookies.set("refresh_token", tokens.refresh_token, { httpOnly: true });
return res;
}See references/sso-client-integration.md for JWKS verification, token refresh, and global logout.
typescript
// app/api/auth/callback/route.ts
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const code = searchParams.get("code");
const verifier = cookies().get("pkce_verifier")?.value;
const response = await fetch(`${SSO_URL}/oauth2/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: process.env.NEXT_PUBLIC_SSO_CLIENT_ID!,
code: code!,
redirect_uri: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback`,
code_verifier: verifier!,
}),
});
const tokens = await response.json();
// Set httpOnly cookies
const res = NextResponse.redirect("/dashboard");
res.cookies.set("access_token", tokens.access_token, { httpOnly: true });
res.cookies.set("refresh_token", tokens.refresh_token, { httpOnly: true });
return res;
}完整的SSO客户端集成教程(包含JWKS验证、令牌刷新和全局登出)请查看 references/sso-client-integration.md。
PKCE Utilities
PKCE工具函数
typescript
// lib/pkce.ts
export function generateCodeVerifier(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64UrlEncode(array);
}
export async function generateCodeChallenge(verifier: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest("SHA-256", data);
return base64UrlEncode(new Uint8Array(hash));
}
function base64UrlEncode(buffer: Uint8Array): string {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}typescript
// lib/pkce.ts
export function generateCodeVerifier(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64UrlEncode(array);
}
export async function generateCodeChallenge(verifier: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest("SHA-256", data);
return base64UrlEncode(new Uint8Array(hash));
}
function base64UrlEncode(buffer: Uint8Array): string {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}Key Patterns
核心模式
1. Token Storage
1. 令牌存储
- Store tokens in httpOnly cookies (not localStorage)
- Use SameSite=Lax for CSRF protection
- 将令牌存储在httpOnly Cookie中(而非localStorage)
- 使用SameSite=Lax以防范CSRF攻击
2. Token Refresh
2. 令牌刷新
typescript
async function refreshTokens() {
const response = await fetch(`${SSO_URL}/oauth2/token`, {
method: "POST",
body: new URLSearchParams({
grant_type: "refresh_token",
client_id: process.env.NEXT_PUBLIC_SSO_CLIENT_ID!,
refresh_token: currentRefreshToken,
}),
});
return response.json();
}typescript
async function refreshTokens() {
const response = await fetch(`${SSO_URL}/oauth2/token`, {
method: "POST",
body: new URLSearchParams({
grant_type: "refresh_token",
client_id: process.env.NEXT_PUBLIC_SSO_CLIENT_ID!,
refresh_token: currentRefreshToken,
}),
});
return response.json();
}3. JWKS Verification
3. JWKS验证
typescript
import { createRemoteJWKSet, jwtVerify } from "jose";
const JWKS = createRemoteJWKSet(
new URL(`${SSO_URL}/.well-known/jwks.json`)
);
export async function verifyAccessToken(token: string) {
const { payload } = await jwtVerify(token, JWKS, {
issuer: SSO_URL,
audience: process.env.NEXT_PUBLIC_SSO_CLIENT_ID,
});
return payload;
}typescript
import { createRemoteJWKSet, jwtVerify } from "jose";
const JWKS = createRemoteJWKSet(
new URL(`${SSO_URL}/.well-known/jwks.json`)
);
export async function verifyAccessToken(token: string) {
const { payload } = await jwtVerify(token, JWKS, {
issuer: SSO_URL,
audience: process.env.NEXT_PUBLIC_SSO_CLIENT_ID,
});
return payload;
}4. Global Logout
4. 全局登出
typescript
// Logout from all apps
const logoutUrl = new URL(`${SSO_URL}/oauth2/logout`);
logoutUrl.searchParams.set("post_logout_redirect_uri", window.location.origin);
window.location.href = logoutUrl.toString();typescript
// Logout from all apps
const logoutUrl = new URL(`${SSO_URL}/oauth2/logout`);
logoutUrl.searchParams.set("post_logout_redirect_uri", window.location.origin);
window.location.href = logoutUrl.toString();Common Pitfalls
常见陷阱
| Issue | Solution |
|---|---|
| PKCE verifier lost after redirect | Store in httpOnly cookie before redirect |
| Token in localStorage | Use httpOnly cookies instead |
| JWKS fetch fails | Check CORS on auth server |
| Consent screen loops | Ensure consent page saves decision |
| 问题 | 解决方案 |
|---|---|
| 重定向后丢失PKCE验证器 | 重定向前将验证器存储在httpOnly Cookie中 |
| 令牌存储在localStorage中 | 改用httpOnly Cookie存储 |
| JWKS获取失败 | 检查认证服务器的CORS设置 |
| 同意页面循环跳转 | 确保同意页面已保存用户决策 |
Verification
验证
Run:
python3 scripts/verify.pyExpected:
✓ configuring-better-auth skill ready运行:
python3 scripts/verify.py预期结果:
✓ configuring-better-auth skill readyIf Verification Fails
若验证失败
- Check: references/ folder has both setup files
- Stop and report if still failing
- 检查:references/目录下是否包含两个搭建文档
- 若仍失败,请停止操作并上报
References
参考资料
- references/auth-server-setup.md - Complete auth server with OIDC provider
- references/sso-client-integration.md - Full SSO client implementation
- references/auth-server-setup.md - 完整的OIDC提供商认证服务器搭建指南
- references/sso-client-integration.md - 完整的SSO客户端集成指南