configuring-better-auth

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Better 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/mcp
Or in
settings.json
:
json
{
  "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.json
中配置:
json
{
  "mcpServers": {
    "better-auth": {
      "type": "http",
      "url": "https://mcp.chonkie.ai/better-auth/better-auth-builder/mcp"
    }
  }
}

When to Use the MCP

何时使用MCP

TaskUse MCP?
Initial Better Auth setupYes - guided configuration
Adding OIDC provider pluginYes - generates correct config
Troubleshooting auth issuesYes - can analyze setup
Understanding auth flowYes - explains concepts
Writing custom middlewareNo - 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-orm
bash
npm install better-auth @better-auth/oidc-provider drizzle-orm

Core 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 jose
bash
npm install jose

Environment Variables

环境变量

env
NEXT_PUBLIC_SSO_URL=http://localhost:3001
NEXT_PUBLIC_SSO_CLIENT_ID=your-client-id
env
NEXT_PUBLIC_SSO_URL=http://localhost:3001
NEXT_PUBLIC_SSO_CLIENT_ID=your-client-id

PKCE 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

常见陷阱

IssueSolution
PKCE verifier lost after redirectStore in httpOnly cookie before redirect
Token in localStorageUse httpOnly cookies instead
JWKS fetch failsCheck CORS on auth server
Consent screen loopsEnsure consent page saves decision

问题解决方案
重定向后丢失PKCE验证器重定向前将验证器存储在httpOnly Cookie中
令牌存储在localStorage中改用httpOnly Cookie存储
JWKS获取失败检查认证服务器的CORS设置
同意页面循环跳转确保同意页面已保存用户决策

Verification

验证

Run:
python3 scripts/verify.py
Expected:
✓ configuring-better-auth skill ready
运行:
python3 scripts/verify.py
预期结果:
✓ configuring-better-auth skill ready

If Verification Fails

若验证失败

  1. Check: references/ folder has both setup files
  2. Stop and report if still failing
  1. 检查:references/目录下是否包含两个搭建文档
  2. 若仍失败,请停止操作并上报

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客户端集成指南