2026/9/16 8:19:30

Wasp 邮箱认证完整实战指南:从注册登录到邮件验证与密码重置

Wasp 邮箱认证完整实战指南:从注册登录到邮件验证与密码重置 Wasp 邮箱认证完整实战指南从注册登录到邮件验证与密码重置【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp本指南以 Wasp 全栈框架当前仓库 waspc 与 web 目录对应的开源实现的邮箱Email认证能力为主线完整讲解如何在 Wasp 应用中启用邮箱注册登录、邮件验证Email Verification与忘记密码Password Reset流程并深入剖析这些流程在生成代码层面的底层实现与安全机制。读完本文你将掌握在main.wasp中声明邮箱认证、配置邮件发送器、定制验证邮件内容、扩展注册字段以及用内置 Auth UI 或手动调用 auth action 两种方式实现完整认证链路的实战方法。邮箱认证能开箱即用地提供什么Wasp 对邮箱认证提供了开箱即用的完整支持包括服务端实现和邮件模板。围绕邮箱认证框架帮你封装好了以下四类能力注册与登录Signup / Login基于邮箱 密码的账号体系密码由服务端自动哈希存储。邮件验证Email Verification注册后向用户邮箱发送验证链接默认只有在验证通过后才允许登录。忘记密码Forgot Password / Password Reset用户可请求重置密码邮件通过带 token 的链接设置新密码。内置 Auth UI 组件生成LoginForm、SignupForm、VerifyEmailForm、ForgotPasswordForm、ResetPasswordForm等现成组件直接拼装进页面即可使用。同时要说明一个当前限制从 version-0.19/auth/_multiple-identities-warning.md 的说明来看目前 Wasp 尚不支持一个用户绑定多个认证身份例如同一个用户不能同时拥有邮箱身份和 Google 身份账号合并Account Merging功能仍在规划中。设置邮箱认证的五个步骤完整的设置流程分为五步最终的main.wasp文件结构大致如下// Configuring e-mail authentication app myApp { auth: { ... }, emailSender: { ... } } // Defining routes and pages route SignupRoute { ... } page SignupPage { ... } // ...下面按步骤展开配置代码以 version-0.19 文档 为准。1. 在 main.wasp 中启用邮箱认证app myApp { wasp: { version: {latestWaspVersion} }, title: My App, auth: { // 1. 指定用户实体下一步会定义它 userEntity: User, methods: { // 2. 启用邮箱认证 email: { // 3. 指定发件人字段 fromField: { name: My App Postman, email: helloitsme.com }, // 4. 指定邮件验证与密码重置选项后面会细讲 emailVerification: { clientRoute: EmailVerificationRoute, }, passwordReset: { clientRoute: PasswordResetRoute, }, }, }, onAuthFailedRedirectTo: /login, onAuthSucceededRedirectTo: / }, }这段声明是整套邮箱认证的核心各字段含义如下auth.userEntity指向你定义的User实体Wasp 会把业务用户与认证数据关联起来。auth.methods.email启用邮箱认证方式email与usernameAndPassword二选一。auth.methods.email.fromField发送验证邮件 / 重置邮件时的发件人姓名与地址。auth.methods.email.emailVerification.clientRoute验证邮件的跳转路由即用户点击邮件链接后进入的前端路由。auth.methods.email.passwordReset.clientRoute重置密码邮件的跳转路由。auth.onAuthFailedRedirectTo未认证用户访问受保护页面authRequired: true时被重定向到的路由。auth.onAuthSucceededRedirectTo登录 / 注册成功后跳转的路由默认值为/该自动跳转仅在启用 Wasp 内置 Auth UI 时生效。2. 添加 User 实体User实体可以精简到只有id字段// 5. 定义用户实体 model User { // highlight-next-line id Int id default(autoincrement()) // 在此下方添加你自己的字段 // ... }关于User实体有两个要点id字段是必需的它可以是任意类型但必须用id标记见 _user-fields.md。除id外你可以自由添加业务字段如果这些字段需要在注册时写入还需同步配置userSignupFields见后文扩展注册字段。User实体如何与整个认证系统关联、如何读取用户数据可进一步阅读 认证实体文档。3. 添加认证相关的路由与页面在main.wasp中声明 5 条路由与页面分别对应登录、注册、请求重置密码、重置密码、邮件验证// ... route LoginRoute { path: /login, to: LoginPage } page LoginPage { component: import { Login } from src/pages/auth } route SignupRoute { path: /signup, to: SignupPage } page SignupPage { component: import { Signup } from src/pages/auth } route RequestPasswordResetRoute { path: /request-password-reset, to: RequestPasswordResetPage } page RequestPasswordResetPage { component: import { RequestPasswordReset } from src/pages/auth, } route PasswordResetRoute { path: /password-reset, to: PasswordResetPage } page PasswordResetPage { component: import { PasswordReset } from src/pages/auth, } route EmailVerificationRoute { path: /email-verification, to: EmailVerificationPage } page EmailVerificationPage { component: import { EmailVerification } from src/pages/auth, }这些组件的 React 实现将写在src/pages/auth.{jsx,tsx}中。4. 创建客户端页面并使用 Auth UI 组件在src/pages下创建auth.{jsx,tsx}引入 Wasp 生成的 Auth UI 组件页面样式使用 Tailwind CSS相关引入方式可参考 project/css-frameworksimport { LoginForm, SignupForm, VerifyEmailForm, ForgotPasswordForm, ResetPasswordForm, } from wasp/client/auth import { Link } from react-router-dom export function Login() { return ( Layout LoginForm / br / span classNametext-sm font-medium text-gray-900 Dont have an account yet? Link to/signupgo to signup/Link. /span br / span classNametext-sm font-medium text-gray-900 Forgot your password? Link to/request-password-resetreset it/Link. /span /Layout ) } export function Signup() { return ( Layout SignupForm / br / span classNametext-sm font-medium text-gray-900 I already have an account (Link to/logingo to login/Link). /span /Layout ) } export function EmailVerification() { return ( Layout VerifyEmailForm / br / span classNametext-sm font-medium text-gray-900 If everything is okay, Link to/logingo to login/Link /span /Layout ) } export function RequestPasswordReset() { return ( Layout ForgotPasswordForm / /Layout ) } export function PasswordReset() { return ( Layout ResetPasswordForm / br / span classNametext-sm font-medium text-gray-900 If everything is okay, Link to/logingo to login/Link /span /Layout ) } // 用于居中内容的布局组件 export function Layout({ children }: { children: React.ReactNode }) { return ( div classNameh-full w-full bg-white div classNameflex min-h-[75vh] min-w-full items-center justify-center div classNameh-full w-full max-w-sm bg-white p-5 div{children}/div /div /div /div ) }通过这种方式邮件验证、请求重置密码、重置密码等流程中从 URL 读取 token 并发送给服务端的繁琐工作全部由 Auth UI 组件代劳。如果想完全自定义登录 / 注册界面可以改用 邮箱认证自定义 UI 的方式手动调用认证 action。更多 Auth UI 组件的用法参见 Auth UI 文档。5. 配置邮件发送器Email Sender验证邮件与重置密码邮件都需要一个邮件发送器。Wasp 开箱支持多个邮件服务商Dummy仅开发环境、Mailgun、SendGrid、Resend以及通用SMTP详见 高级邮件文档。为快速跑通流程先用Dummy提供商——它不会真正发信而是把邮件内容打印到控制台app myApp { // ... // 7. 设置邮件发送器 emailSender: { provider: Dummy, } }需要特别注意的是Dummy提供商仅限开发环境使用。从 _dummy-provider-note.md 的说明可知如果用Dummy提供商执行生产构建构建会直接失败。收尾迁移数据库并启动完成上述配置后依次运行wasp db migrate-dev wasp start即可得到一个带邮箱认证的可运行应用。想为某些页面开启登录保护只需在页面声明中加上authRequired: true未登录用户会被重定向到onAuthFailedRedirectTo指定的路由详见 认证总览文档。登录与注册流程的内置防护行为使用邮箱认证后登录和注册流程默认带有以下几项安全防护注册限流Rate limiting同一邮箱地址的注册请求被限制为每分钟 1 次用于防止垃圾注册。防止邮箱枚举Preventing user email leaks如果有人用一个已存在且已验证的邮箱注册服务端会假装注册成功而不是提示邮箱已被占用从而避免泄露已有用户的邮箱地址。允许未验证邮箱重复注册Allowing registration for unverified emails如果用户用一个已存在但未验证的邮箱注册Wasp 会允许其重新注册。这是为了防止恶意用户抢先占用他人邮箱、永久阻止邮箱主人注册。密码校验Password validation默认要求密码非空、长度至少 8 位且包含数字。校验规则与覆盖方式见 认证总览中的默认校验。源码视角注册防护是怎么实现的从生成代码可以印证上述行为。在 signup.ts 模板 中注册路由的处理逻辑如下对已存在且已验证的邮箱身份调用doFakeWork()模拟耗时后直接返回{ success: true }刻意与真实注册行为保持一致防止通过响应差异枚举邮箱对已存在但未验证的邮箱身份检查isEmailResendAllowed(providerData, emailVerificationSentAt)判断距离上次发送验证邮件是否满足时间间隔不满足则抛出400 Please wait X secs before trying again.满足则删除旧用户并重新创建参数校验由ensureValidEmail、ensurePasswordIsPresent、ensureValidPassword完成见 validation.ts 模板邮箱必须非空且格式合法密码必须非空、长度 ≥ 8 且包含数字密码通过sanitizeAndSerializeProviderData序列化时自动哈希绝不会以明文落库创建成功后调用createEmailVerificationLink(email, clientRoute)生成带 JWT token 的验证链接并通过sendEmailVerificationEmail发送。开发模式下跳过邮件验证默认情况下Wasp 要求邮箱验证通过后才允许登录。但在开发阶段每次注册都走一遍邮件验证很繁琐也影响自动化测试的编写。为此可以在.env.server中设置环境变量SKIP_EMAIL_VERIFICATION_IN_DEVtrue该变量的底层逻辑在 config/email.ts 模板 中仅在isDevelopment为真时isEmailAutoVerified才会读取env.SKIP_EMAIL_VERIFICATION_IN_DEV进而由 signup.ts 模板 将isEmailVerified直接置为true并跳过发信生产构建中该值恒为false。邮件验证流程Email Verification默认注册完成后Wasp 会向用户邮箱发送验证邮件。邮件中的链接指向emailVerification.clientRoute指定的路由本例即EmailVerificationRoute路径为/email-verification// ... emailVerification: { clientRoute: EmailVerificationRoute, }用户点击链接进入验证页后页面需要从 URL 取出 token 并交给服务端验证。如果你用了 Auth UI 的VerifyEmailForm这一步已自动完成手动实现时则调用verifyEmailactionimport { verifyEmail } from wasp/client/auth // ... await verifyEmail({ token });源码视角verifyEmail 的实现在 verifyEmail.ts 模板 中服务端处理流程为用validateJWT校验并解析 token 中的emailtoken 非法则抛出400 Email verification failed, invalid token通过findAuthIdentity(createProviderId(email, email))查找邮箱身份找不到同样报错防止枚举将providerData.isEmailVerified更新为true触发onAfterEmailVerifiedHook钩子供业务方在验证完成后执行自定义逻辑如欢迎邮件、积分发放等详见 auth-hooks.md。定制验证邮件内容默认验证邮件内容由生成代码提供见 config/email.ts 模板 中未定义getEmailContentFn时的兜底实现主题为 Verify your email正文包含验证链接。你可以通过getEmailContentFn字段完全自定义app myApp { // ... auth: { methods: { email: { // ... emailVerification: { clientRoute: EmailVerificationRoute, getEmailContentFn: import { getVerificationEmailContent } from src/auth/email, }, }, }, }, }对应的实现文件注意 TypeScript 类型GetVerificationEmailContentFn从wasp/server/auth导入函数接收verificationLink并返回subject/text/html三部分import { GetVerificationEmailContentFn } from wasp/server/auth export const getVerificationEmailContent: GetVerificationEmailContentFn ({ verificationLink, }) ({ subject: Verify your email, text: Click the link below to verify your email: ${verificationLink}, html: pClick the link below to verify your email/p a href${verificationLink}Verify email/a , })密码重置流程Password Reset用户可以在/request-password-reset页面输入邮箱发起重置请求随后收到一封带重置链接的邮件链接指向passwordReset.clientRoute指定的路由本例即PasswordResetRoute路径为/password-reset用户在那里输入新密码完成重置// ... passwordReset: { clientRoute: PasswordResetRoute, }该流程同样内置了两项安全防护限流同一邮箱的密码重置请求同样限制为每分钟 1 次。防止信息泄露如果请求重置的邮箱不存在服务端会返回与重置成功完全一致的响应避免攻击者通过响应差异判断邮箱是否注册过。手动实现时两个关键 action 分别是requestPasswordReset与resetPasswordimport { requestPasswordReset } from wasp/client/auth // ... await requestPasswordReset({ email });import { resetPassword } from wasp/client/auth // ... await resetPassword({ password, token })源码视角请求重置与重置的实现在 requestPasswordReset.ts 模板 中先用ensureValidEmail校验邮箱邮箱身份不存在时执行doFakeWork()模拟耗时再返回成功从响应时间上增加邮箱枚举难度源码注释明确说明了这一设计意图身份存在时通过isEmailResendAllowed(providerData, passwordResetSentAt)做限流通过后调用createPasswordResetLink生成链接并发送邮件。在 resetPassword.ts 模板 中先校验 token、再校验密码源码注释说明这是为了让持有无效 token 的未认证调用者无法探测部署环境的密码策略token 解析失败返回400 Password reset failed, invalid token更新hashedPassword时自动重新哈希同时把isEmailVerified置为true——即成功重置密码即视为邮箱已验证调用invalidateAllSessionsForAuthId使该用户所有现存会话失效防止会话被他人继续使用。定制重置密码邮件内容与验证邮件类似通过passwordReset.getEmailContentFn定制app myApp { // ... auth: { methods: { email: { // ... passwordReset: { clientRoute: PasswordResetRoute, getEmailContentFn: import { getPasswordResetEmailContent } from src/auth/email, }, }, }, }, }import { GetPasswordResetEmailContentFn } from wasp/server/auth export const getPasswordResetEmailContent: GetPasswordResetEmailContentFn ({ passwordResetLink, }) ({ subject: Password reset, text: Click the link below to reset your password: ${passwordResetLink}, html: pClick the link below to reset your password/p a href${passwordResetLink}Reset password/a , })密码相关的校验辅助函数Wasp 在wasp/server/auth对应生成模板 validation.ts中提供了两个可直接复用的密码校验函数ensurePasswordIsPresent(args)检查密码是否存在缺失则抛出校验错误。ensureValidPassword(args)检查密码是否合法长度 ≥ 8 且包含数字不合法则抛出校验错误具体规则见 认证总览的默认校验。扩展注册字段userSignupFields如果需要保存邮箱、密码之外的额外注册字段如address、phone需要做两件事。第一步服务端声明字段在main.wasp中给email方法加上userSignupFields引用app myApp { // ... auth: { userEntity: User, methods: { email: { // ... userSignupFields: import { userSignupFields } from src/auth, // ... }, }, }, }然后在src/auth.{js,ts}中定义字段处理函数。userSignupFields是一个对象键是字段名必须与User实体上的字段一一对应值是接收客户端提交数据的函数函数返回要写入数据库的值数据非法时抛错import { defineUserSignupFields } from wasp/server/auth export const userSignupFields defineUserSignupFields({ address: async (data) { const address data.address if (typeof address ! string) { throw new Error(Address is required) } if (address.length 5) { throw new Error(Address must be at least 5 characters long) } return address }, })两点提醒不要把password放进userSignupFields密码由 Wasp 认证后端单独处理自动哈希防止以明文落库也可以在字段函数里使用任意校验库例如zod的safeParse来做更复杂的校验示例见 认证总览的注册字段定制。从 signup.ts 模板 可以看到userSignupFields传入validateAndGetUserFields后被用于校验与写库同时该模板也证明了这些字段处理函数会在onBeforeSignupHook之后执行因此钩子可以先行否决通过抛错整个注册流程。第二步在 SignupForm 中展示字段使用 Auth UI 时通过SignupForm的additionalFieldsprop 添加额外字段它可以是对象列表或渲染函数二者可混用import { SignupForm, FormError, FormInput, FormItemGroup, FormLabel, } from wasp/client/auth export const SignupPage () { return ( SignupForm additionalFields{[ /* address 用对象定义 */ { name: address, label: Address, type: input, validations: { required: Address is required, }, }, /* phoneNumber 用渲染函数定义 */ (form, state) { return ( FormItemGroup FormLabelPhone Number/FormLabel FormInput {...form.register(phoneNumber, { required: Phone number is required, })} disabled{state.isLoading} / {form.formState.errors.phoneNumber ( FormError {form.formState.errors.phoneNumber.message} /FormError )} /FormItemGroup ) }, ]} / ) }对象形式的字段支持name、label必填、type可选input/textarea与validations校验规则对象键为校验名、值为错误提示规则体系与react-hook-form的register一致。渲染函数签名如下type AdditionalSignupFieldRenderFn ( hookForm: UseFormReturn, formState: FormState ) React.ReactNode其中form是react-hook-form对象需要用form.register注册字段state是表单状态含isLoading: boolean表示是否正在提交。如果你不使用 Auth UI 而是自定义注册界面则只需在自定义表单中提交这些额外字段即可无需配置additionalFields。读取用户的邮箱认证数据拿到user对象后客户端通过useAuth()或受保护页面的userprop服务端通过context.user具体见 认证总览的访问登录用户可以通过user.identities.email访问邮箱认证相关的全部数据字段说明来自 entities/_email-data.mdconst emailIdentity user.identities.email // 用户注册时使用的邮箱地址例如 fluffyllamaapp.com emailIdentity.id // 邮箱是否已验证true 表示已验证 emailIdentity.isEmailVerified // 最后一次发送验证邮件的时间 emailIdentity.emailVerificationSentAt // 最后一次发送密码重置邮件的时间 emailIdentity.passwordResetSentAt关于认证数据的整体模型User、Auth、AuthIdentity如何关联可继续阅读 认证实体文档 中的 Accessing the Auth Fields 章节。email 字典完整字段速查以下是auth.methods.email支持的全部配置项对应文档 API Reference 章节app myApp { title: My app, // ... auth: { userEntity: User, methods: { email: { userSignupFields: import { userSignupFields } from src/auth, fromField: { name: My App, email: helloitsme.com }, emailVerification: { clientRoute: EmailVerificationRoute, getEmailContentFn: import { getVerificationEmailContent } from src/auth/email, }, passwordReset: { clientRoute: PasswordResetRoute, getEmailContentFn: import { getPasswordResetEmailContent } from src/auth/email, }, }, }, onAuthFailedRedirectTo: /someRoute }, // ... }字段类型必填说明userSignupFieldsExtImport否注册时写入User的额外字段定义见上文扩展注册字段fromFieldEmailFromField是发件人信息name为发件人名称email为发件人邮箱email必填emailVerificationEmailVerificationConfig是邮件验证配置clientRoute必填指向处理验证 token 的客户端路由getEmailContentFn可选自定义验证邮件内容passwordResetPasswordResetConfig是密码重置配置clientRoute必填指向处理重置 token 与新密码的客户端路由getEmailContentFn可选自定义重置邮件内容emailVerification.clientRoute指定的页面需要完成从 URL 取 token 并交给服务端的工作可用verifyEmailactionpasswordReset.clientRoute指定的页面则需要完成读取 token、收集新密码并提交的工作可用requestPasswordReset/resetPasswordaction——使用 Auth UI 时这些都由生成组件代劳。从声明到路由生成代码如何串起整个链路最后从生成器模板层面梳理邮箱认证的完整服务端链路。在 config/email.ts 模板 中Wasp 会为邮箱认证生成一个 Express Router注册如下路由POST /login→getLoginRoute登录POST /signup→getSignupRoute注册含验证邮件发送POST /request-password-reset→getRequestPasswordResetRoute请求重置邮件POST /reset-password→resetPassword重置密码并作废全部会话POST /verify-email→verifyEmail验证邮箱模板还演示了配置注入方式fromField、emailVerificationClientRoute、passwordResetClientRoute由声明生成getEmailContentFn未定义时使用内置默认邮件模板SKIP_EMAIL_VERIFICATION_IN_DEV仅在开发环境生效。这些模板位于 waspc/data/Generator/templates/server/src/auth/providers/email是理解邮箱认证内部机制的绝佳起点对应的客户端 auth actionrequestPasswordReset、resetPassword、verifyEmail等则生成在 waspc/data/Generator/templates/sdk/wasp/auth/email。总结在 Wasp 中启用邮箱认证只需五步在main.wasp声明auth.methods.email、定义User实体、添加五条认证路由与页面、用 Auth UI 组件拼装页面、配置emailSender。之后 Wasp 会自动为你交付完整的注册 / 登录、邮件验证、密码重置服务端实现并内置注册限流、邮箱枚举防护、未验证邮箱重注册、密码重置防枚举与 token 校验等安全机制。更进一步你可以通过userSignupFields扩展注册字段、通过getEmailContentFn定制两类邮件内容、通过SKIP_EMAIL_VERIFICATION_IN_DEV加速开发调试而生成模板 waspc/data/Generator/templates/server/src/auth/providers/email 则为你理解底层原理提供了完整参考。【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考