资讯详情

资讯详情

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/waspWasp 为全栈 JS/TS 应用提供了开箱即用的邮箱认证Email Authentication涵盖登录、注册、邮箱验证Email Verification与忘记密码Password Reset完整流程且服务端逻辑与邮件模板均由框架内置生成。本指南以 Wasp v0.15 的邮箱认证文档为主体结合当前仓库中实际生成的认证代码模板逐步演示如何在一个 Wasp 应用中启用邮箱认证、理解其内置的安全行为速率限制、邮箱防泄露等并在必要时通过自定义 sign-up action 与邮件内容函数进行深度扩展。Wasp 邮箱认证能为你做什么启用邮箱认证后Wasp 会自动生成并提供以下能力无需你手写任何认证逻辑服务端注册、登录、验证邮箱、请求/重置密码接口五套现成的 Auth UI 组件LoginForm、SignupForm、VerifyEmailForm、ForgotPasswordForm、ResetPasswordForm可直接嵌入你的 React 页面所有认证邮件的默认模板验证邮件、密码重置邮件且支持通过getEmailContentFn自定义内容内置安全行为注册与密码重置的速率限制、防止用户邮箱泄露的伪装响应、允许未验证邮箱重新注册等。需要留意的是Waspv0.15目前不支持单个用户同时拥有多个认证身份——例如一个用户不能既拥有邮箱身份又拥有 Google 身份这是未来 account merging 功能wasp-lang/wasp#954 相关规划要解决的问题。当前版本中一个用户只能绑定一种认证方式。从零启用邮箱认证5 步实操启用邮箱认证需要依次完成以下 5 个步骤在main.wasp文件中启用邮箱认证添加User实体添加认证路由route与页面page在页面中使用 Auth UI 组件配置邮件发送器Email Sender。最终得到的main.wasp文件结构大致如下// Configuring e-mail authentication app myApp { auth: { ... } } // Defining routes and pages route SignupRoute { ... } page SignupPage { ... } // ...第 1 步在main.wasp中启用邮箱认证在main.wasp中声明app时通过auth字段配置认证。开启邮箱认证的核心是methods.email配置块app myApp { wasp: { version: ^0.15.0 }, title: My App, auth: { // 1. 指定用户实体下一步定义 userEntity: User, methods: { // 2. 启用邮箱认证 email: { // 3. 指定发件人from字段 fromField: { name: My App Postman, email: helloitsme.com }, // 4. 指定邮箱验证与密码重置选项后面详述 emailVerification: { clientRoute: EmailVerificationRoute, }, passwordReset: { clientRoute: PasswordResetRoute, }, }, }, onAuthFailedRedirectTo: /login, onAuthSucceededRedirectTo: / }, }JavaScript 与 TypeScript 项目中的main.wasp写法完全一致上述配置两种项目通用。各字段职责userEntity指定承载用户数据的实体即下文要定义的Usermethods.email启用邮箱认证方式的配置入口methods.email.fromField声明认证邮件验证邮件、重置邮件的发件人名称与地址methods.email.emailVerification.clientRoute邮箱验证邮件中链接所指向的前端路由methods.email.passwordReset.clientRoute密码重置邮件中链接所指向的前端路由onAuthFailedRedirectTo/onAuthSucceededRedirectTo登录失败/成功后的跳转路径。email配置块的全部可选字段userSignupFields、fromField、emailVerification、passwordReset将在文末的 API Reference 中逐一说明。第 2 步添加User实体User实体可以非常简单只包含一个id字段即可满足认证系统的最低要求// 5. 定义用户实体 model User { id Int id default(autoincrement()) // 在下方添加你自己的字段 // ... }User实体必须包含一个标记为id的id字段类型不限其余字段均可按业务需要自由添加。凡是需要在注册时写入的额外字段还需要在userSignupFields中声明见下文 API Reference。User与认证系统其余部分的关联方式、以及如何访问用户数据可参考认证实体相关文档。第 3 步添加认证路由与页面在main.wasp中声明 5 个认证页面及其对应路由// ... route LoginRoute { path: /login, to: LoginPage } page LoginPage { component: import { Login } from src/pages/auth.tsx } route SignupRoute { path: /signup, to: SignupPage } page SignupPage { component: import { Signup } from src/pages/auth.tsx } route RequestPasswordResetRoute { path: /request-password-reset, to: RequestPasswordResetPage } page RequestPasswordResetPage { component: import { RequestPasswordReset } from src/pages/auth.tsx, } route PasswordResetRoute { path: /password-reset, to: PasswordResetPage } page PasswordResetPage { component: import { PasswordReset } from src/pages/auth.tsx, } route EmailVerificationRoute { path: /email-verification, to: EmailVerificationPage } page EmailVerificationPage { component: import { EmailVerification } from src/pages/auth.tsx, }若项目使用 JavaScript将src/pages/auth.tsx改为src/pages/auth.jsx即可。注意EmailVerificationRoute与PasswordResetRoute正是第 1 步中emailVerification.clientRoute与passwordReset.clientRoute所引用的路由——邮件中的链接会指向这两个前端路径由对应的页面解析 token 并调用服务端接口。第 4 步编写客户端页面使用 Auth UI 组件在src/pages目录下创建auth.tsx或auth.jsx从wasp/client/auth导入 Wasp 生成的 Auth UI 组件并组装页面。以下为 TypeScript 版本JavaScript 版本仅需去掉类型注解import { 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 ) }这里我们导入的是 Wasp 根据认证配置自动生成的 Auth UI 组件它们内部已经处理了表单交互、token 解析与发送、错误提示等全部细节因此页面代码非常精简。页面样式示例使用了 Tailwind CSSLayout组件的 className 即为其用法关于如何在 Wasp 中接入 Tailwind CSS可参考项目文档中 CSS 框架配置相关章节。Auth UI 组件的完整用法可进一步阅读 Auth UI 文档。第 5 步配置邮件发送器邮箱验证与密码重置流程都依赖真实的邮件发送因此必须配置邮件发送器。Wasp 开箱支持多种邮件服务商Dummy仅开发用、Mailgun、SendGrid、Resend以及通用SMTP。为快速跑通流程先用Dummy提供商——它不会真正发送邮件而是把邮件内容打印到控制台无需任何额外配置app myApp { // ... // 7. 配置邮件发送器 emailSender: { provider: Dummy, } }⚠️Dummy仅用于开发阶段如果用Dummy提供商执行生产构建构建会直接失败这是框架有意为之的约束。生产环境请切换为真实服务商SMTP在.env.server中配置SMTP_HOST、SMTP_USERNAME、SMTP_PASSWORD、SMTP_PORT多数事务邮件服务商Mailgun、SendGrid 等也支持走 SMTPMailgun/SendGrid/Resend在.env.server中配置对应的 API Key 与域名等凭据。各种 provider 的详细配置方式参见 发送邮件文档。跑起来看看完成以上 5 步后在项目根目录依次执行wasp db migrate-dev wasp start即可得到一个带完整邮箱认证的应用。如果希望某些页面只允许登录用户访问可参考 Auth 概览文档。开箱即用的登录与注册行为除了界面之外注册与登录流程默认内置了以下安全行为均可在 注册路由模板源码 中验证1. 注册速率限制Wasp 将每个邮箱地址的注册请求限制为每分钟 1 次用于防止注册刷量spamming。从源码看isEmailResendAllowed见 邮件工具模板默认的重发间隔就是1000 * 60毫秒即 60 秒若距上次发送不足 1 分钟服务端会返回Please wait X secs before trying again.的错误提示。2. 防止用户邮箱泄露如果有人尝试用一个已存在且已验证的邮箱注册Wasp 会假装注册成功而不是提示该账户已存在从而避免攻击者探测哪些邮箱已经注册过。源码中这一步通过doFakeWork()模拟耗时后直接返回{ success: true }实现——返回的响应与真实注册成功完全一致攻击者无法通过响应差异判断邮箱是否已被占用。3. 允许未验证邮箱重新注册如果用户尝试注册一个已存在但未验证的邮箱Wasp 会允许其重新注册。原因在于若不这样做攻击者可以用别人的邮箱注册并故意不验证从而永久锁死该邮箱让真正的主人无法注册。源码逻辑是检查距上次发送验证邮件的时间若在重发间隔内则直接拒绝Please wait X secs...否则删除旧的未验证用户并创建新用户再发送新的验证邮件。4. 密码校验注册时会对密码做格式校验ensureValidPassword。默认的密码规则及自定义方式见 Auth 概览文档。邮箱验证流程深入默认情况下Wasp 要求邮箱必须通过验证后才能登录。流程是注册成功后系统向用户邮箱发送一封验证邮件用户点击邮件中的链接完成验证。验证配置块如下// ... emailVerification: { clientRoute: EmailVerificationRoute, }用户点击邮件中的链接后会跳转到clientRoute指定的前端路由本例为/email-verification。验证页面的职责是从 URL 中取出 token 并交给服务端完成验证——VerifyEmailForm组件自动处理了这一切。开发模式跳过验证开发调试或编写自动化测试时可以跳过邮箱验证步骤。在.env.server文件中设置SKIP_EMAIL_VERIFICATION_IN_DEVtrue设置后注册的邮箱会被自动标记为已验证无需每次注册都走一遍验证流程。验证背后的实现细节结合仓库中的生成模板验证流程的实际运作机制如下链接生成utils.tscreateEmailVerificationLink(email, clientRoute)用用户邮箱签发一个30 分钟有效期的 JWT拼成${frontendUrl}${clientRoute}?token${jwtToken}形式的链接。验证邮件发送时还会把emailVerificationSentAt时间戳写入身份数据供速率限制使用服务端验证verifyEmail.tsverifyEmail接口先用validateJWT校验 token无效则返回400 Email verification failed, invalid token再根据 token 中的邮箱找到认证身份将其isEmailVerified置为true最后触发onAfterEmailVerifiedHook钩子——该钩子可用于在用户完成邮箱验证后执行自定义逻辑kitchen-sink 示例项目中即有对钩子调用次数的验证测试客户端调用若不走 Auth UI 组件也可以手动调用框架生成的 actionimport { verifyEmail } from wasp/client/auth ... await verifyEmail({ token });验证邮件的默认内容主题、文本、HTML同样可以自定义通过getEmailContentFn实现详见文末 API Reference。密码重置流程用户可以通过忘记密码入口请求重置密码随后收到一封包含重置链接的邮件点击后进入设置新密码的页面。密码重置配置块如下// ... passwordReset: { clientRoute: PasswordResetRoute, }与邮箱验证一致邮件中的链接指向clientRoute指定的前端路由本例为/password-reset用户在该页面输入新密码完成重置。内置安全行为密码重置流程同样内置了两项防护源码位于 requestPasswordReset 相关模板 与 resetPassword 模板速率限制每个邮箱地址的密码重置请求限制为每分钟 1 次防止邮件轰炸防止信息泄露如果请求重置的邮箱不存在服务端会返回与成功请求完全相同的响应避免攻击者借此探测邮箱是否已注册。重置密码的实现细节resetPassword服务端逻辑值得特别说明先校验 tokenensureTokenIsPresent与validateJWT再校验密码格式——模板源码注释明确指出先验证 token 是为了防止未认证调用者通过无效 token 探测部署环境的密码策略重置密码的同时会把isEmailVerified置为true重置密码这个动作本身就证明了邮箱所有权因此顺带完成了邮箱验证新密码在写入数据库时会经过哈希处理修改密码后调用invalidateAllSessionsForAuthId使该用户所有现有会话全部失效确保即使有人窃取了会话也无法继续使用。手动调用框架生成的 action 的方式如下import { requestPasswordReset } from wasp/client/auth ... await requestPasswordReset({ email });import { resetPassword } from wasp/client/auth ... await resetPassword({ password, token })使用ForgotPasswordForm/ResetPasswordForm组件则无需手动处理这些调用。自定义 Sign-up Action进阶:::caution 谨慎使用自定义注册 Action 除非有充分理由否则不建议自定义注册 action。该过程较为复杂容易犯错并危及应用安全。 :::默认注册逻辑由框架生成。如果确有必要例如注册时需要执行额外业务逻辑、接入风控或第三方服务可以定义自己的 action。在main.wasp中声明// ... action customSignup { fn: import { signup } from src/auth/signup.ts, }对应的实现TypeScript 版本利用wasp/server/auth导出的底层工具函数与框架内置注册逻辑保持一致的校验与安全行为import { ensurePasswordIsPresent, ensureValidPassword, ensureValidEmail, createProviderId, sanitizeAndSerializeProviderData, deserializeAndSanitizeProviderData, findAuthIdentity, createUser, createEmailVerificationLink, sendEmailVerificationEmail, } from wasp/server/auth import type { CustomSignup } from wasp/server/operations type CustomSignupInput { email: string password: string } type CustomSignupOutput { success: boolean message: string } export const signup: CustomSignup CustomSignupInput, CustomSignupOutput async (args, _context) { ensureValidEmail(args) ensurePasswordIsPresent(args) ensureValidPassword(args) try { const providerId createProviderId(email, args.email) const existingAuthIdentity await findAuthIdentity(providerId) if (existingAuthIdentity) { const providerData deserializeAndSanitizeProviderDataemail( existingAuthIdentity.providerData ) // 在此添加你的自定义逻辑 } else { // sanitizeAndSerializeProviderData 会对用户密码进行哈希 const newUserProviderData await sanitizeAndSerializeProviderDataemail({ hashedPassword: args.password, isEmailVerified: false, emailVerificationSentAt: null, passwordResetSentAt: null, }) await createUser( providerId, newUserProviderData, // 想存到 User 实体上的任何附加数据 {} ) // 验证链接指向客户端路由例如 /email-verification const verificationLink await createEmailVerificationLink( args.email, /email-verification ) try { await sendEmailVerificationEmail(args.email, { from: { name: My App Postman, email: helloitsme.com, }, to: args.email, 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 , }) } catch (e: unknown) { console.error(Failed to send email verification email:, e) throw new HttpError(500, Failed to send email verification email.) } } } catch (e) { return { success: false, message: e.message, } } // 注册完成后的自定义代码 // ... return { success: true, message: User created successfully, } }JavaScript 版本写法相同仅去掉类型标注并将文件命名为signup.js、导入路径中的CustomSignup类型移除。建议复用框架内置的字段校验器同样从wasp/server/auth导入与默认注册流程内部使用的校验器完全一致邮箱校验ensureValidEmail(args)校验邮箱格式不合法则抛出错误。默认校验规则见 Auth 概览文档。密码校验ensurePasswordIsPresent(args)校验密码是否已提供未提供则抛出错误。ensureValidPassword(args)校验密码是否符合规则不合规则抛出错误。默认规则同样见 Auth 概览文档。访问已登录用户的邮箱数据无论在前端还是服务端拿到user对象后即可通过其identities.email访问邮箱认证身份的数据结构定义见 email-data 片段const emailIdentity user.identities.email // 用户注册时使用的邮箱地址例如 fluffyllamaapp.com emailIdentity.id // 是否为 true用户是否已验证邮箱 emailIdentity.isEmailVerified // 上次发送邮箱验证邮件的时间 emailIdentity.emailVerificationSentAt // 上次发送密码重置邮件的时间 emailIdentity.passwordResetSentAt在客户端与服务端获取当前登录用户的完整方法参见 Auth 概览文档 中的相关章节。API Referenceemail 认证配置项详解userEntity字段userEntity指向的用户实体必须满足以下要求必须包含id字段类型不限但必须标记为id可以添加任意其他字段但若这些字段需要在注册时写入还必须同步在userSignupFields中声明见下文。app myApp { title: My app, // ... auth: { userEntity: User, methods: { email: { // 以下选项逐一说明 }, }, onAuthFailedRedirectTo: /someRoute }, // ... }model User { id Int id default(autoincrement()) }email配置块的全部字段app myApp { title: My app, // ... auth: { userEntity: User, methods: { email: { userSignupFields: import { userSignupFields } from src/auth.ts, fromField: { name: My App, email: helloitsme.com }, emailVerification: { clientRoute: EmailVerificationRoute, getEmailContentFn: import { getVerificationEmailContent } from src/auth/email.ts, }, passwordReset: { clientRoute: PasswordResetRoute, getEmailContentFn: import { getPasswordResetEmailContent } from src/auth/email.ts, }, }, }, onAuthFailedRedirectTo: /someRoute }, // ... }userSignupFields: ExtImportuserSignupFields定义了注册过程中需要在User实体上设置的所有额外字段。例如User上有address和phone字段可以这样声明import { defineUserSignupFields } from wasp/server/auth export const userSignupFields defineUserSignupFields({ address: (data) { if (!data.address) { throw new Error(Address is required) } return data.address }, phone: (data) data.phone, })每个字段对应一个(data) value函数返回的值会写入User对应字段函数内可以抛错来拒绝注册如上面的address必填校验。更完整的说明见 Auth 概览文档。fromField: EmailFromField必填fromField是一个 dict指定应用发出邮件的发件人名称与邮箱地址name发件人名称email发件人邮箱地址必填。emailVerification: EmailVerificationConfig必填emailVerification是描述邮箱验证流程的配置 dict包含以下字段clientRoute: Route必填用户验证邮箱所用到的路由。该客户端路由需要负责从 URL 中取出 token 并发送给服务端完成验证。可以直接使用框架生成的verifyEmailactionimport { verifyEmail } from wasp/client/auth ... await verifyEmail({ token });前文使用 Auth UIVerifyEmailForm正是为了省去手动发送 token 的这步工作。getEmailContentFn: ExtImport返回发送给用户的验证邮件内容的函数。在src目录下新建文件定义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 , })函数接收{ verificationLink }GetVerificationEmailContentFn类型定义见 email SDK 模板返回包含subject、text、html三个字段的邮件内容。上述代码即默认邮件内容可自由定制。该函数的签名表明邮件内只需拼接收到的verificationLink即可实现点击跳转验证。passwordReset: PasswordResetConfig必填passwordReset是描述密码重置流程的配置 dict包含以下字段clientRoute: Route必填用户重置密码所用到的路由。该客户端路由需要负责从 URL 中取出 token、收集用户输入的新密码并发送给服务端。可以使用框架生成的requestPasswordReset与resetPasswordactionimport { requestPasswordReset } from wasp/client/auth ... await requestPasswordReset({ email });import { resetPassword } from wasp/client/auth ... await resetPassword({ password, token })前文使用 Auth UIForgotPasswordForm/ResetPasswordForm正是为了省去手动发送请求的这步工作。getEmailContentFn: ExtImport返回密码重置邮件内容的函数写法与验证邮件类似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 , })函数接收{ passwordResetLink }返回subject、text、html三个字段。上述代码为默认内容可自行定制。小结至此你已经在 Wasp 应用中完整集成了邮箱认证通过main.wasp的 5 步配置启用了登录、注册、邮箱验证与密码重置理解了框架内置的速率限制、邮箱防泄露、未验证邮箱重新注册等安全机制及其在 signup.ts 等模板源码中的实现并掌握了通过自定义 sign-up action、userSignupFields与getEmailContentFn扩展认证流程的方法。更进一步的登录/登出按钮配置、密码规则自定义等话题可继续阅读 Auth 概览文档 与 Auth UI 文档。【免费下载链接】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),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →