认证系统配置
Better Auth 配置、OAuth 提供商设置、手机号登录、Magic Link、2FA 和微信登录集成指南
概览
项目使用 Better Auth 作为认证框架。Better Auth 是一个开源的 TypeScript 认证库,帮你处理用户注册、登录、会话管理等核心功能,无需从零实现。
- 配置位置:
apps/01mvp-web/src/lib/auth/auth-config.ts - 环境变量:
apps/01mvp-web/.env.local
登录方式一览
项目支持以下登录方式,默认已开启邮箱 + 密码登录,其余方式需配置对应环境变量后才会生效。
| 登录方式 | 所需环境变量 | 默认状态 |
|---|---|---|
| 邮箱 + 密码 | BETTER_AUTH_SECRET, DATABASE_URL | 已开启 |
| GitHub OAuth | GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET | 未配置则隐藏 |
| Google OAuth | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | 未配置则隐藏 |
| 微信扫码 (PC) | WECHAT_WEBSITE_APP_ID, WECHAT_WEBSITE_APP_SECRET | 未配置则隐藏 |
| 微信授权 (手机) | WECHAT_SERVICE_ACCOUNT_APP_ID, WECHAT_SERVICE_ACCOUNT_APP_SECRET | 未配置则隐藏 |
| 微信小程序 | WECHAT_MINIPROGRAM_APP_ID, WECHAT_MINIPROGRAM_APP_SECRET | 未配置则隐藏 |
| 手机号 + 短信验证码 | SMS_PROVIDER 及对应短信服务商的密钥 | 已开启(需配置短信服务) |
| Magic Link(邮件链接) | 邮件服务配置(EMAIL_FROM 等) | 已开启 |
| 用户名登录 | 无需额外配置 | 已开启 |
| 双因素认证 (2FA) | config.auth.enableTwoFactor(代码配置) | 已开启 |
什么是 OAuth? OAuth 是一种让用户通过第三方账号(如 GitHub、Google、微信)登录的协议,省去了用户单独注册和记密码的步骤。你只需在对应平台注册应用、拿到 Client ID 和 Secret 填入环境变量即可。
环境变量配置
基础配置
这些变量是认证系统运行的必要条件。
# 必填:Better Auth 签名密钥(至少 32 字符,用于加密 session token)
BETTER_AUTH_SECRET=your-secret-key-min-32-chars
# 可选:应用 URL(默认从请求中推断,建议显式设置)
BETTER_AUTH_URL=http://localhost:7001
# 数据库连接(Prisma 使用 PostgreSQL)
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
# 可选:信任的来源域名(逗号分隔,用于 CORS 和 CSRF 防护)
TRUSTED_ORIGINS=https://app.example.com,https://www.example.com
# 可选:跨子域名 Cookie(实现单点登录 SSO)
COOKIE_DOMAIN=.example.comBETTER_AUTH_SECRET 在生产环境必须使用随机生成的强密钥,绝对不要使用示例中的值。可以用 openssl rand -base64 32 生成。
OAuth 提供商配置
| 环境变量 | 说明 | 用于 |
|---|---|---|
GITHUB_CLIENT_ID | GitHub OAuth App 的 Client ID | GitHub 登录 |
GITHUB_CLIENT_SECRET | GitHub OAuth App 的 Client Secret | GitHub 登录 |
GOOGLE_CLIENT_ID | Google Cloud Console 的 Client ID | Google 登录 |
GOOGLE_CLIENT_SECRET | Google Cloud Console 的 Client Secret | Google 登录 |
微信登录配置
| 环境变量 | 说明 |
|---|---|
WECHAT_WEBSITE_APP_ID | 微信开放平台网站应用 AppID(PC 扫码登录) |
WECHAT_WEBSITE_APP_SECRET | 微信开放平台网站应用 AppSecret |
WECHAT_SERVICE_ACCOUNT_APP_ID | 微信公众平台服务号 AppID(手机端授权) |
WECHAT_SERVICE_ACCOUNT_APP_SECRET | 微信公众平台服务号 AppSecret |
WECHAT_MINIPROGRAM_APP_ID | 微信小程序 AppID |
WECHAT_MINIPROGRAM_APP_SECRET | 微信小程序 AppSecret |
短信服务配置
根据你选择的短信服务商,配置对应的环境变量。通过 SMS_PROVIDER 指定服务商(默认 tencent)。
| 环境变量 | 说明 | 服务商 |
|---|---|---|
SMS_PROVIDER | 短信服务商:tencent / aliyun / twilio | 通用 |
TENCENT_CLOUD_SECRET_ID | 腾讯云 SecretId | 腾讯云 |
TENCENT_CLOUD_SECRET_KEY | 腾讯云 SecretKey | 腾讯云 |
TENCENT_CLOUD_REGION | 腾讯云地域(默认 ap-guangzhou) | 腾讯云 |
TENCENT_SMS_SDK_APP_ID | 腾讯云短信 SDK AppID | 腾讯云 |
TENCENT_SMS_SIGN_NAME | 短信签名 | 腾讯云 |
TENCENT_SMS_TEMPLATE_ID | 短信模板 ID | 腾讯云 |
ALIYUN_ACCESS_KEY_ID | 阿里云 AccessKeyId | 阿里云 |
ALIYUN_ACCESS_KEY_SECRET | 阿里云 AccessKeySecret | 阿里云 |
ALIYUN_SMS_SIGN_NAME | 短信签名 | 阿里云 |
ALIYUN_SMS_TEMPLATE_CODE | 短信模板 Code | 阿里云 |
TWILIO_ACCOUNT_SID | Twilio Account SID | Twilio |
TWILIO_AUTH_TOKEN | Twilio Auth Token | Twilio |
TWILIO_FROM_PHONE_NUMBER | Twilio 发送号码 | Twilio |
邮件服务配置
| 环境变量 | 说明 |
|---|---|
EMAIL_FROM | 发件人邮箱地址(用于验证邮件、Magic Link 等) |
ZEABUR_EMAIL_API_KEY | Zeabur Email API 密钥 |
所有环境变量都在 apps/01mvp-web/.env.local 中配置。复制 .env.example 作为起点:cp apps/01mvp-web/.env.example apps/01mvp-web/.env.local
OAuth 登录配置
创建 OAuth App
访问 GitHub Developer Settings,点击 "New OAuth App"。
填写应用信息
- Application name: 你的应用名称
- Homepage URL:
http://localhost:7001(生产环境改为正式域名) - Authorization callback URL:
http://localhost:7001/api/auth/callback/github
获取凭证并配置环境变量
创建后拿到 Client ID,再生成 Client Secret,填入 .env.local:
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret生产环境注意事项
部署到生产环境时,需回到 GitHub 更新 Homepage URL 和 callback URL 为正式域名。回调 URL 必须精确匹配,包括协议(https)、域名和路径。
创建 OAuth 凭据
访问 Google Cloud Console,进入 "APIs & Services" > "Credentials",点击 "Create Credentials" > "OAuth client ID"。
配置 OAuth 同意屏幕
首次创建时需要先配置 OAuth 同意屏幕(OAuth consent screen),选择 "External" 用户类型,填写应用信息。
填写回调 URI
- Application type 选择 "Web application"
- Authorized redirect URIs 添加:
http://localhost:7001/api/auth/callback/google
配置环境变量
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secretGoogle Cloud Console 里的 "Authorized redirect URIs" 必须和实际回调地址完全一致。多一个斜杠、少一个端口号都会失败。
微信登录是三种模式中最复杂的,需要分别配置 PC 端、手机端和小程序。项目已在 auth-config.ts 中预集成微信插件,配置好环境变量即可使用。
配置回调域名
在应用设置中填写回调域名。注意:去掉 http:// 前缀,不含端口号。例如开发环境填 localhost,生产环境填已备案域名。
获取凭证
拿到 AppID 和 AppSecret,填入环境变量。可以按需只配置一种模式:
# PC 端(网站应用)
WECHAT_WEBSITE_APP_ID=wx1234567890abcdef
WECHAT_WEBSITE_APP_SECRET=your-website-app-secret
# 移动端(服务号)
WECHAT_SERVICE_ACCOUNT_APP_ID=wx0987654321fedcba
WECHAT_SERVICE_ACCOUNT_APP_SECRET=your-service-account-secret
# 小程序
WECHAT_MINIPROGRAM_APP_ID=wx1234567890abcdef
WECHAT_MINIPROGRAM_APP_SECRET=your-miniprogram-app-secret实现账号互通(可选)
如果需要 PC、移动端和小程序账号互通(同一用户在不同端登录识别为同一人),在微信开放平台将所有应用绑定到同一个开放平台账号。绑定后,同一用户会返回相同的 unionid,系统会自动关联账户。
内置能力说明:
- 微信 OAuth 的 Token 交换端点(
/api/auth/wechat/token)已内置:接收前端传来的code,调用微信接口换取access_token,返回给 Better Auth 完成登录流程。 - 账户关联逻辑已内置:当用户通过不同端的微信登录时,系统会根据
unionId自动关联到已有账户,无需手动处理。 - 新用户注册时会自动生成唯一用户名(基于 nanoid),无需用户手动填写。
手机号登录
手机号登录使用短信验证码方式,用户输入手机号后收到 6 位验证码,输入即可登录或注册。
选择短信服务商
项目支持腾讯云短信、阿里云短信和 Twilio 三种服务商。默认使用腾讯云。
配置短信服务
在微信开放平台 / 阿里云 / Twilio 控制台申请短信签名和模板,获取凭证后填入 .env.local:
# 选择服务商(tencent / aliyun / twilio)
SMS_PROVIDER=tencent
# 以腾讯云为例
TENCENT_CLOUD_SECRET_ID=your-secret-id
TENCENT_CLOUD_SECRET_KEY=your-secret-key
TENCENT_SMS_SDK_APP_ID=your-sdk-app-id
TENCENT_SMS_SIGN_NAME=your-sign-name
TENCENT_SMS_TEMPLATE_ID=your-template-id确认手机号登录路由
手机号登录的 API 路由已内置,无需额外配置。前端页面路径为 /auth/login/phone,API 端点由 Better Auth 的 phoneNumber 插件自动生成。
短信验证码有效期为 5 分钟,允许 3 次验证尝试。手机号验证通过后会自动注册新用户(如果该手机号尚未注册)。手机号格式会通过 @01mvp/utils 包中的 validateFullPhoneNumber 自动校验,支持国际号码。
Magic Link 登录
Magic Link(魔法链接)是一种无密码登录方式:用户输入邮箱后,系统发送一封包含登录链接的邮件,用户点击链接即自动登录,无需记忆密码。
当前配置:
- Magic Link 已在
auth-config.ts中启用 disableSignUp: true表示 Magic Link 仅用于已注册用户的登录,不支持通过 Magic Link 直接注册新账户- 登录链接由邮件服务(Zeabur Email)发送,模板 ID 为
magicLink
前端使用:在登录页面通过 authClient.signIn.magicLink({ email }) 发起请求,用户收到邮件后点击链接即可完成登录。
如果不想使用 Magic Link 登录,可在 apps/01mvp-web/src/lib/config/index.ts 中将 auth.enableMagicLink 设为 false。注意这不会移除插件,只影响前端是否展示该选项。
双因素认证 (2FA)
双因素认证(Two-Factor Authentication)为账户增加第二层安全验证。用户在输入密码后,还需要通过手机验证码或 TOTP 应用(如 Google Authenticator)确认身份。
当前配置:
- 2FA 插件已在
auth-config.ts中加载 - 通过
config.auth.enableTwoFactor控制开关(默认true) - 使用 Better Auth 内置的
twoFactor插件,支持 TOTP(基于时间的一次性密码)
启用/禁用:在 apps/01mvp-web/src/lib/config/index.ts 中修改:
auth: {
enableTwoFactor: true, // 改为 false 可禁用
}前端使用:通过 authClient.twoFactor.enable() 让用户绑定 2FA,通过 authClient.twoFactor.verifyTOTP() 验证 TOTP 码。
2FA 一旦启用,用户在登录后会进入二次验证流程。请确保前端有对应的验证页面和 UI 提示,避免用户被卡在登录流程中。
管理员角色
项目内置了基于角色的权限管理系统,通过 Better Auth 的 admin 插件和 @01mvp/auth 包的 permissions 模块实现。
角色类型
| 角色 | 标识 | 说明 |
|---|---|---|
| 超级管理员 | super_admin | 全部权限,包括系统配置和角色管理 |
| 运营管理员 | operation_admin | 用户管理、内容审核、勋章管理等运营权限 |
| 兼容角色 | admin | 向后兼容,等同于 super_admin |
权限列表
管理员权限细分为以下类别:
- 用户管理:
VIEW_USERS、MANAGE_USERS、BAN_USERS、ASSIGN_ROLES - 职能角色:
VIEW_FUNCTIONAL_ROLES、MANAGE_FUNCTIONAL_ROLES - 贡献管理:
VIEW_CONTRIBUTIONS、REVIEW_CONTRIBUTIONS - 勋章管理:
VIEW_BADGES、MANAGE_BADGES、AWARD_BADGES - 组织管理:
VIEW_ORGANIZATIONS、MANAGE_ORGANIZATIONS - 系统配置:
VIEW_SYSTEM_CONFIG、MANAGE_SYSTEM_CONFIG、MANAGE_SYSTEM - 仪表板:
VIEW_DASHBOARD
使用方式
import { isAdmin, hasPermission, AdminPermission } from "@01mvp/auth";
// 检查是否为管理员
if (isAdmin(user)) {
// 管理员逻辑
}
// 检查特定权限
if (hasPermission(user, AdminPermission.MANAGE_USERS)) {
// 用户管理逻辑
}用户的角色存储在 User.role 字段中。可以通过 Better Auth 的 admin 插件 API(authClient.admin.setRole())来分配角色。
Better Auth 插件一览
项目预装了以下 Better Auth 插件,开箱即用:
| 插件 | 说明 |
|---|---|
admin | 管理员角色和权限管理 |
magicLink | 邮件魔法链接登录(无密码) |
openAPI | 自动生成 OpenAPI 文档 |
twoFactor | 双因素认证(TOTP) |
username | 用户名登录支持 |
phoneNumber | 手机号 + 短信验证码登录 |
wechatOAuth(自定义) | 微信 OAuth 登录(自定义插件) |
在代码中使用
客户端:获取当前用户
在 React 组件中使用 useSession hook 获取当前登录状态:
"use client";
import { useSession } from "@/modules/shared/auth/hooks/use-session";
export function UserProfile() {
const { user, session, loaded } = useSession();
if (!loaded) return <div>加载中...</div>;
if (!user) return <div>未登录</div>;
return (
<div>
<p>用户名: {user.name}</p>
<p>邮箱: {user.email}</p>
</div>
);
}客户端:调用登录 API
使用 authClient 发起各种认证操作:
import { authClient } from "@/lib/auth/client";
// 邮箱密码登录
await authClient.signIn.email({
email: "user@example.com",
password: "password123",
});
// GitHub 登录(跳转到 GitHub 授权页)
await authClient.signIn.social({ provider: "github" });
// 手机号验证码登录
await authClient.phoneNumber.sendOtp({ phoneNumber: "+8613800138000" });
await authClient.phoneNumber.verify({ phoneNumber: "+8613800138000", code: "123456" });
// Magic Link 登录
await authClient.signIn.magicLink({ email: "user@example.com" });
// 登出
await authClient.signOut();服务端:在 API 路由和 Server Components 中
使用 getSession 获取服务端会话(已自动缓存):
import { getSession } from "@/modules/shared/auth/lib/server";
// 在 Server Component 中
export default async function DashboardPage() {
const session = await getSession();
if (!session) {
redirect("/auth/login");
}
return <div>欢迎, {session.user.name}</div>;
}
// 在 API 路由中
export async function GET() {
const session = await getSession();
if (!session) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
// ... 业务逻辑
}服务端:权限检查
import { isAdmin, hasPermission, AdminPermission } from "@01mvp/auth";
import { getSession } from "@/modules/shared/auth/lib/server";
export async function GET() {
const session = await getSession();
if (!session || !isAdmin(session.user)) {
return Response.json({ error: "Forbidden" }, { status: 403 });
}
// 管理员逻辑
}