跳到主要内容

用户认证集成

用户认证是大多数 SaaS 应用的核心功能。Vibe CLI 支持多种主流的认证服务提供商,让你可以快速为应用添加用户注册、登录、权限管理等功能。

🚀 快速开始

bash
# 交互式安装认证集成
vibe install auth

# 直接指定提供商
vibe install auth --provider=clerk

📦 支持的提供商

Clerk - 现代化认证服务

特点:

  • 🎨 开箱即用的 UI 组件
  • 🔐 支持多种社交登录
  • 📱 多因素认证 (MFA)
  • 👥 用户管理面板
  • 🌍 国际化支持

安装:

bash
vibe install auth --provider=clerk

配置示例:

bash
? 是否启用社交登录: Yes
? 选择社交登录平台: Google, GitHub, Apple
? 是否启用多因素认证: Yes
? 自定义域名 (可选): auth.myapp.com

NextAuth - Next.js 官方方案

特点:

  • ⚡ Next.js 深度集成
  • 🔧 高度可定制
  • 🛡️ 安全性优先
  • 📚 丰富的提供商支持
  • 🗄️ 多种数据库支持

安装:

bash
vibe install auth --provider=nextauth

配置示例:

bash
? 选择认证提供商: Google, GitHub, Discord
? 选择数据库: PostgreSQL (Supabase)
? 是否启用邮箱验证: Yes
? JWT 密钥策略: Auto-generate

Supabase Auth - 开源解决方案

特点:

  • 🆓 开源免费
  • 🗄️ 与数据库深度集成
  • 📧 内置邮箱验证
  • 🔑 行级安全 (RLS)
  • 📱 移动端支持

安装:

bash
vibe install auth --provider=supabase

配置示例:

bash
? Supabase 项目 URL: https://xxx.supabase.co
? 是否启用社交登录: Yes
? 选择社交登录平台: Google, GitHub
? 是否启用邮箱确认: Yes

🛠️ 安装后配置

1. 环境变量设置

根据选择的提供商,需要配置相应的环境变量:

Clerk

bash
# .env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx
CLERK_SECRET_KEY=sk_test_xxx

NextAuth

bash
# .env.local
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-secret-key
GOOGLE_CLIENT_ID=xxx
GOOGLE_CLIENT_SECRET=xxx
GITHUB_CLIENT_ID=xxx
GITHUB_CLIENT_SECRET=xxx

Supabase

bash
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJxxx
SUPABASE_SERVICE_ROLE_KEY=eyJxxx

2. 获取 API 密钥

Clerk

  1. 访问 Clerk Dashboard
  2. 创建新应用或选择现有应用
  3. 在 "API Keys" 页面复制密钥
  4. 配置认证设置和社交登录

NextAuth

  1. Google: 访问 Google Cloud Console
  2. GitHub: 访问 GitHub Developer Settings
  3. Discord: 访问 Discord Developer Portal

Supabase

  1. 访问 Supabase Dashboard
  2. 创建新项目或选择现有项目
  3. 在 "Settings > API" 页面复制密钥
  4. 配置认证设置

📝 使用示例

Clerk 集成示例

tsx
pages/_app.tsx
import { ClerkProvider } from '@clerk/nextjs'

function MyApp({ Component, pageProps }) {
return (
<ClerkProvider>
<Component {...pageProps} />
</ClerkProvider>
)
}

export default MyApp
tsx
components/AuthButton.tsx
import { SignInButton, SignOutButton, useUser } from '@clerk/nextjs'

export function AuthButton() {
const { isSignedIn, user } = useUser()

if (isSignedIn) {
return (
<div>
<span>Hello, {user.firstName}!</span>
<SignOutButton />
</div>
)
}

return <SignInButton />
}

NextAuth 集成示例

tsx
pages/api/auth/[...nextauth].ts
import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
import GitHubProvider from 'next-auth/providers/github'

export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
],
})
tsx
components/AuthButton.tsx
import { useSession, signIn, signOut } from 'next-auth/react'

export function AuthButton() {
const { data: session } = useSession()

if (session) {
return (
<div>
<span>Hello, {session.user?.name}!</span>
<button onClick={() => signOut()}>退出登录</button>
</div>
)
}

return <button onClick={() => signIn()}>登录</button>
}

Supabase 集成示例

tsx
lib/supabase.ts
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

export const supabase = createClient(supabaseUrl, supabaseAnonKey)
tsx
components/AuthButton.tsx
import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabase'

export function AuthButton() {
const [user, setUser] = useState(null)

useEffect(() => {
const { data: { user } } = supabase.auth.getUser()
setUser(user)

const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
setUser(session?.user ?? null)
}
)

return () => subscription.unsubscribe()
}, [])

const handleSignIn = async () => {
await supabase.auth.signInWithOAuth({
provider: 'google',
})
}

const handleSignOut = async () => {
await supabase.auth.signOut()
}

if (user) {
return (
<div>
<span>Hello, {user.email}!</span>
<button onClick={handleSignOut}>退出登录</button>
</div>
)
}

return <button onClick={handleSignIn}>登录</button>
}

🔧 高级配置

自定义认证流程

tsx
components/CustomAuth.tsx
import { useState } from 'react'
import { useAuth } from '../hooks/useAuth'

export function CustomAuth() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const { signIn, signUp, loading } = useAuth()

const handleSignIn = async (e) => {
e.preventDefault()
await signIn(email, password)
}

const handleSignUp = async (e) => {
e.preventDefault()
await signUp(email, password)
}

return (
<form>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="邮箱"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="密码"
/>
<button onClick={handleSignIn} disabled={loading}>
登录
</button>
<button onClick={handleSignUp} disabled={loading}>
注册
</button>
</form>
)
}

权限管理

tsx
components/ProtectedRoute.tsx
import { useAuth } from '../hooks/useAuth'
import { useRouter } from 'next/router'
import { useEffect } from 'react'

interface ProtectedRouteProps {
children: React.ReactNode
requiredRole?: string
}

export function ProtectedRoute({ children, requiredRole }: ProtectedRouteProps) {
const { user, loading } = useAuth()
const router = useRouter()

useEffect(() => {
if (!loading && !user) {
router.push('/login')
}

if (requiredRole && user?.role !== requiredRole) {
router.push('/unauthorized')
}
}, [user, loading, requiredRole, router])

if (loading) {
return <div>加载中...</div>
}

if (!user) {
return null
}

if (requiredRole && user.role !== requiredRole) {
return <div>权限不足</div>
}

return <>{children}</>
}

🎨 自定义样式

Clerk 样式自定义

tsx
pages/_app.tsx
import { ClerkProvider } from '@clerk/nextjs'

const clerkAppearance = {
variables: {
colorPrimary: '#3b82f6',
colorText: '#1f2937',
colorBackground: '#ffffff',
colorInputBackground: '#f9fafb',
colorInputText: '#1f2937',
borderRadius: '8px',
},
elements: {
formButtonPrimary: 'bg-blue-600 hover:bg-blue-700',
card: 'shadow-lg',
},
}

function MyApp({ Component, pageProps }) {
return (
<ClerkProvider appearance={clerkAppearance}>
<Component {...pageProps} />
</ClerkProvider>
)
}

NextAuth 样式自定义

tsx
pages/api/auth/[...nextauth].ts
import NextAuth from 'next-auth'

export default NextAuth({
theme: {
colorScheme: 'light',
brandColor: '#3b82f6',
logo: '/logo.png',
},
pages: {
signIn: '/auth/signin',
signUp: '/auth/signup',
error: '/auth/error',
},
})

🐛 常见问题

1. 认证状态不同步

问题: 用户登录后状态没有更新

解决方案:

tsx
// 确保在组件顶层使用认证钩子
const { user, loading } = useAuth()

// 或者手动刷新状态
const refreshAuth = async () => {
await supabase.auth.getSession()
}

2. 重定向问题

问题: 登录后没有正确重定向

解决方案:

tsx
// 设置正确的回调 URL
const handleSignIn = async () => {
await signIn('google', {
callbackUrl: '/dashboard'
})
}

3. 环境变量问题

问题: API 密钥配置错误

解决方案:

bash
# 检查环境变量
echo $NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY

# 重启开发服务器
npm run dev

📚 最佳实践

  1. 安全性: 始终使用 HTTPS,保护敏感信息
  2. 用户体验: 提供清晰的错误信息和加载状态
  3. 性能: 使用适当的缓存策略
  4. 可访问性: 确保认证界面符合无障碍标准
  5. 测试: 编写认证流程的单元测试和集成测试

🎯 下一步

完成认证集成后,你可以: