首页
/ Payload Auth 示例:在 Next.js 中构建完整前后端认证的实战指南

Payload Auth 示例:在 Next.js 中构建完整前后端认证的实战指南

2026-09-05 11:04:25作者:龚格成

Payload 的 Auth 示例(examples/auth)演示了如何把 Payload 认证能力完整接入一个 Next.js 全栈应用:服务端通过 Local API(payload.auth)校验会话,前端通过 REST 或 GraphQL 双通道调用认证操作,配合角色化访问控制、CORS/CSRF 与 Cookie 安全配置,实现“管理员管后台、普通用户走前台”的典型权限模型。读完本文,你可以本地跑起这个示例,理解 users 集合的认证配置、字段钩子与访问函数的实现细节,并掌握生产环境构建部署的完整流程。

示例定位与代码地图

这个示例对应仓库中的 examples/auth/README.md,配套代码位于 examples/auth/src 目录。核心文件分布如下:

文件/目录 职责
src/collections/Users.ts 认证集合 users 的完整定义(auth 配置、access、字段、钩子)
src/collections/access/ adminsadminsAndUseranyonecheckRole 等访问控制函数
src/collections/hooks/protectRoles.ts 保护角色字段、保证每个用户至少有 user 角色的字段钩子
src/collections/hooks/loginAfterCreate.ts 注册后自动登录的 afterChange 钩子
src/payload.config.ts Payload 主配置(数据库、CORS/CSRF、secret 等)
src/migrations/seed.ts 种子迁移,创建演示管理员账号
src/app/(app)/_providers/Auth//_providers/Auth) 前端认证上下文,封装 REST 与 GraphQL 两套调用通道
src/app/(app)/login/LoginForm/index.tsx/login/LoginForm/index.tsx) 登录表单,演示重定向与错误处理

示例包含 /login/logout/create-account/recover-password/reset-password/account 等前台页面,以及 http://localhost:3000/admin 的后台面板,覆盖认证的全生命周期。

快速启动

第一步:从示例创建项目

npx create-payload-app --example auth

第二步:配置数据库

该示例使用 MongoDB 适配器(见 payload.config.ts 中的 mongooseAdapter),需要一个可访问的 MongoDB 实例。从 .env.example 复制配置,仓库中 examples/auth/.env.example 定义了 4 个环境变量:

# 数据库连接串
DATABASE_URL=mongodb://127.0.0.1/payload-example-auth
# 用于加密 JWT Token
PAYLOAD_SECRET=YOUR_SECRET_HERE
# 用于配置 CORS、格式化链接等(末尾不要加斜杠)
NEXT_PUBLIC_SERVER_URL=http://localhost:3000
# 用于跨子域共享 Cookie
COOKIE_DOMAIN=localhost

若 MongoDB 实例需要鉴权,直接把凭证拼进 DATABASE_URL

第三步:启动服务并注入种子数据

pnpm dev

结合 package.json 可以看到,dev 脚本实际上是 pnpm seed && next dev,其中 seed 定义为 payload migrate:fresh——即启动前先执行一次全量迁移并重建数据库。当启动过程提示是否注入种子数据时,输入 y 并回车即可。种子迁移(seed.ts)会创建一个管理员账号:

await payload.create({
  collection: 'users',
  data: {
    email: 'demo@payloadcms.com',
    password: 'demo',
    roles: ['admin'],
  },
})

注意:seed 是破坏性操作,它会 drop 当前数据库并用种子模板重建。只适合新项目或可以接受数据丢失的场景。

第四步:访问应用

  • 打开 http://localhost:3000 访问前台首页;
  • 打开 http://localhost:3000/admin 访问后台面板;
  • 使用演示凭证登录后台:Email: demo@payloadcms.comPassword: demo

Users 集合:认证核心

整个示例只有一个集合 users,但它是认证体系的全部核心。Users.ts 的关键配置如下:

export const Users: CollectionConfig = {
  slug: 'users',
  auth: {
    tokenExpiration: 28800, // 8 小时
    cookies: {
      sameSite: 'none',
      secure: true,
      domain: process.env.COOKIE_DOMAIN,
    },
  },
  access: {
    read: adminsAndUser,
    create: anyone,
    update: adminsAndUser,
    delete: admins,
    unlock: admins,
    admin: ({ req: { user } }) => checkRole(['admin'], user),
  },
  hooks: {
    afterChange: [loginAfterCreate],
  },
  // fields 省略,下文展开
}

几个值得注意的设计点:

  1. tokenExpiration: 28800:JWT 有效期 8 小时,过期后客户端需要通过 Refresh Token 操作续期;
  2. Cookie 三件套sameSite: 'none' + secure: true + 可配置的 domain,这是为了支持前台站点与后台 API 分离部署(跨子域)时仍能安全携带认证 Cookie。COOKIE_DOMAIN 环境变量在 payload.config.ts 之外的集合层被读取;
  3. saveToJWTroles 字段设置了该属性(见下文字段部分),角色会直接写入 JWT,服务端判断权限时无需查库;
  4. loginAfterCreate:注册后自动登录(见后文钩子部分)。

字段设计

集合字段体现了“认证所需字段全部显式声明”的思路:

  • emailemail 类型,required + unique,且字段级 access 限制为 adminsAndUser(自己或管理员可读可改);
  • passwordpassword 类型,后台编辑时提示“留空则保持当前密码”;
  • resetPasswordToken / resetPasswordExpiration:隐藏字段,支撑忘记密码流程;
  • firstName / lastName:基础资料;
  • rolesselect 类型 + hasMany,取值 admin / usersaveToJWT: true,字段级 access 仅管理员可读写,并挂上 protectRoles 钩子。

基于角色的访问控制

README 定义了两种角色:

  • admin:可访问 Payload 后台面板,可看到全部数据、执行全部操作;
  • user:不能访问后台面板,只能基于自身进行受限操作。

具体实现分布在 src/collections/access/ 下的四个文件:

// checkRole.ts:判断用户是否拥有目标角色中的任意一个
export const checkRole = (allRoles: User['roles'] = [], user: User | null = null): boolean => {
  if (user) {
    if (allRoles.some((role) => user?.roles?.some((individualRole) => individualRole === role))) {
      return true
    }
  }
  return false
}

// admins.ts:仅管理员
export const admins: Access = ({ req: { user } }) => checkRole(['admin'], user)

// anyone.ts:任何人(用于注册)
export const anyone: Access = () => true

adminsAndUser 最有意思——它不只是布尔判断,而是返回查询约束,实现“管理员看全部、普通用户只看自己”:

export const adminsAndUser: Access = ({ req: { user } }) => {
  if (user) {
    if (checkRole(['admin'], user)) {
      return true
    }
    return {
      id: { equals: user.id }, // 普通用户只能读写自己的文档
    }
  }
  return false
}

这套组合落到 users 集合上就是:任何人可注册(create: anyone)、登录后可读/改自己的账户(read/update: adminsAndUser)、只有管理员能删除用户或解锁账号(delete/unlock: admins)、只有管理员能进入后台管理用户(admin)。

protectRoles 钩子:角色防篡改

protectRoles.ts 挂在 roles 字段的 beforeChange 上,解决两个问题:新用户创建时自动带上 user 角色;非管理员永远改不了角色。

export const protectRoles: FieldHook<{ id: string } & User> = ({ data, req }) => {
  const isAdmin =
    req.user?.roles.includes('admin') || data.email === 'demo@payloadcms.com' // for the seed script

  if (!isAdmin) {
    return ['user']
  }

  const userRoles = new Set(data?.roles || [])
  userRoles.add('user')
  return [...userRoles]
}

从源码结构看,data.email === 'demo@payloadcms.com' 这一行是专门为 seed 脚本放行的:种子迁移在进程内创建管理员时,请求上下文里还没有登录用户,因此用邮箱白名单保证 admin 角色不被钩子覆盖。

loginAfterCreate 钩子:注册即登录

loginAfterCreate.ts 挂在集合的 afterChange 上,当 operation === 'create' 且请求体里同时带 emailpassword 时,自动调用 Local API 的 payload.login 完成登录,并把 tokenuser 附加到响应文档中:

if (operation === 'create') {
  const { email, password } = body
  if (email && password) {
    const { token, user } = await payload.login({
      collection: 'users',
      data: { email, password },
      req,
      res,
    })
    return { ...doc, token, user }
  }
}
return doc

这让前端注册页无需二次调用登录接口,一次请求即可完成“建账号 + 建会话”。

双 API 认证:Local API 与 HTTP

Payload 的认证能力同时暴露在服务端 Local API 和 HTTP(REST/GraphQL)两层,示例中两者都有真实落地。

Local API:服务端校验会话

在 Next.js 服务端组件中,通过 getPayload 拿到实例后用 payload.auth({ headers }) 从请求头解析当前用户。account/page.tsx/account/page.tsx) 的真实实现与 README 给出的片段一致:

import { headers as getHeaders } from 'next/headers.js'
import { getPayload } from 'payload'
import config from '../../../payload.config'

export default async function Account() {
  const headers = await getHeaders()
  const payload = await getPayload({ config })
  const { permissions, user } = await payload.auth({ headers })

  if (!user) {
    redirect(
      `/login?error=${encodeURIComponent('You must be logged in to access your account.')}&redirect=/account`,
    )
  }
  return ...
}

未登录时重定向到 /login,并通过 errorredirect 查询参数传递提示信息;登录页会用 getSafeRedirectpayload/shared)处理回跳目标,见 LoginForm/index.tsx/login/LoginForm/index.tsx)。

HTTP:REST 与 GraphQL 操作端点

开启 auth 后,users 集合自动暴露一组认证操作端点:MeLoginLogoutRefresh TokenVerify EmailUnlockForgot PasswordReset Password。最典型的调用是获取当前用户:

await fetch('/api/users/me', {
  method: 'GET',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json',
  },
})

服务端没有 Local API 可用时(例如独立前端、边缘环境),这些 HTTP API 依然可用。

示例更进一步,在前端封装了双通道认证 Provider(_providers/Auth/index.tsx/_providers/Auth/index.tsx)):AuthProvider 接受 api: 'rest' | 'gql' 属性,createloginlogoutforgotPasswordresetPassword 每个操作都同时实现了 REST 与 GraphQL 两种调用方式,页面只需 useAuth() 取用,切换底层协议零成本。配套的两个底层客户端:

  • rest.ts/_providers/Auth/rest.ts):统一 fetch 封装,credentials: 'include' 携带 Cookie,从响应体取 user,遇到 errors 抛错;
  • gql.ts/_providers/Auth/gql.ts):向 /api/graphqlPOST,同样的 Cookie 携带策略。

Provider 挂载时(useEffect)会立即调用 /api/users/me(或 GraphQL 的 meUser 查询)拉取当前用户写入 React Context,实现刷新页面后登录态自动恢复。

安全配置:CORS、CSRF 与 Cookies

README 强调该示例已配置 corscsrfcookies,确保后台与前台安全通信。对照源码,payload.config.ts 中有两处显式配置:

export default buildConfig({
  admin: {
    components: {
      beforeLogin: ['@/components/BeforeLogin#BeforeLogin'], // 登录页自定义组件
    },
  },
  collections: [Users],
  cors: [process.env.NEXT_PUBLIC_SERVER_URL || ''].filter(Boolean),
  csrf: [process.env.NEXT_PUBLIC_SERVER_URL || ''].filter(Boolean),
  db: mongooseAdapter({ url: process.env.DATABASE_URL || '' }),
  secret: process.env.PAYLOAD_SECRET || '',
  // ...
})

三者的分工:

  1. cors:只允许 NEXT_PUBLIC_SERVER_URL 声明的前端源跨域访问 API,杜绝任意站点发起请求;
  2. csrf:同源/同域表单请求需要携带 CSRF Token,防止跨站请求伪造;
  3. cookies(集合层 auth.cookies):sameSite: 'none' 允许跨站携带、secure: true 强制 HTTPS、domain: process.env.COOKIE_DOMAIN 支持跨子域共享会话——这套组合正是为“前台站点域名与 API 域名分离”的部署形态设计的。

另外,PAYLOAD_SECRET 负责 JWT 的签名与加密,.env.example 中明确要求替换占位值,生产环境务必使用强随机密钥。

生产构建与部署

Payload 在生产环境需要构建并独立托管 Admin 面板,示例给出两步:

  1. 在项目根目录执行 pnpm build(等价 yarn build / npm run build)触发 payload build,生成 ./build 目录下的生产级 admin bundle。对照 package.json,脚本为 cross-env NODE_OPTIONS=--no-deprecation payload build
  2. 启动 Node.js 服务从 ./build 目录托管 Payload。仓库脚本表中该入口为 "start": "next start",即 pnpm start(README 中写作 pnpm serve,以实际 package.json 脚本名为准)。

部署方面 README 给出了两条路径:集成 Next.js 的应用可直接部署到 Vercel;也可使用 Payload Cloud 从 GitHub 仓库一键部署生产实例;手动部署则参考 Payload 官方文档的部署章节。

小结

examples/auth 的价值在于它用最小集合展示了 Payload 认证的完整链路:配置层auth.tokenExpiration、Cookie 策略、saveToJWT)决定会话与凭证形态;访问层admins / adminsAndUser / checkRole)用“布尔 + 查询约束”两种返回值实现角色化权限;钩子层protectRolesloginAfterCreate)在数据流上守住角色完整性与注册即登录体验;API 层则让同一套认证能力在 Next.js 服务端(Local API)与浏览器(REST/GraphQL)两侧以不同姿势复用。理解这个示例后,你可以按同样骨架扩展自己的应用:替换字段、增加角色、按需收紧或放宽 access 函数,即可得到一套类型安全、前后端一致的认证系统。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384