ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Wasp 邮箱认证自定义 UI 完整指南:用 wasp/client/auth 打造登录、注册与密码重置流程

Wasp 邮箱认证自定义 UI 完整指南:用 wasp/client/auth 打造登录、注册与密码重置流程 Wasp 邮箱认证自定义 UI 完整指南用 wasp/client/auth 打造登录、注册与密码重置流程【免费下载链接】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邮件认证email auth是 Wasp 框架中最常用的认证方式之一用户使用邮箱和密码注册Wasp 校验数据并发送验证邮件账户在用户点击验证链接之前保持非激活状态忘记密码时则通过类似的流程重置密码。虽然 Wasp 提供了开箱即用的 Auth UI但在实际项目中你往往需要与品牌一致的表单样式、定制化的交互反馈或是把认证流程嵌入已有的页面设计中。本指南将以 Wasp 0.17 版本官方文档为主体带你从零实现一套完全自控的邮箱认证 UI覆盖登录、注册、邮箱验证、请求重置密码与重置密码五个完整流程并通过仓库中的 SDK 与服务端源码讲清每个函数背后的真实调用链与安全细节。邮箱认证的整体工作流在深入代码之前先理解 Wasp 邮箱认证的状态机这决定了 UI 的每一个分支应该如何设计注册Signup用户提交邮箱与密码Wasp 在服务端校验数据合法性随后向该邮箱发送一封验证邮件。此时账户已创建但处于未激活状态用户也不会被登录。邮箱验证Email Verification用户点击邮件中的链接链接携带token查询参数前端调用verifyEmail({ token })服务端验证 token 后将账户标记为激活。在此之前用户无法正常使用该账户登录。登录Login账户激活后用户用邮箱与密码登录成功后 Wasp 会建立会话session前端应据此进行页面跳转。请求密码重置Request Password Reset用户忘记密码时输入邮箱Wasp 发送一封密码重置邮件。注意此动作不会立即重置密码只是发送邮件。重置密码Password Reset用户点击邮件中的链接携带token提交新密码后完成重置随后可凭新密码登录。关于默认的邮箱与密码校验规则可阅读 auth overview docs 中的 “Default validations” 一节。Wasp 的预置 Auth UI 本质上就是基于本指南将要讲到的这些函数实现的。当你需要更多自定义空间时完全可以效仿它的做法在你的客户端代码中直接调用 Wasp 的 auth actions。从wasp/client/auth导入认证函数自定义 UI 的核心依赖只有一个模块wasp/client/auth。在 SDK 的公开入口模板 中可以看到Wasp 生成器会把五个邮箱认证函数统一从这里导出// PUBLIC API export { login } from ../../auth/email/actions/login export { signup } from ../../auth/email/actions/signup export { requestPasswordReset, resetPassword } from ../../auth/email/actions/passwordReset export { verifyEmail } from ../../auth/email/actions/verifyEmail也就是说你只需一条 import 语句即可拿到全部能力import { login, requestPasswordReset, resetPassword, signup, verifyEmail, } from wasp/client/auth完整示例代码五个认证组件下面是一份可直接作为起点的客户端实现官方文档原始示例它包含处理登录、注册、邮箱验证与密码重置流程所需的全部组件。你可以自定义任何外观和行为只要确保调用的是从wasp/client/auth导入的函数即可。示例使用useState管理表单状态与错误信息使用react-router-dom的useNavigate在成功后跳转页面。JavaScript 版本src/pages/auth.jsximport { login, requestPasswordReset, resetPassword, signup, verifyEmail, } from wasp/client/auth import { useState } from react import { useNavigate } from react-router-dom // This will be shown when the user wants to log in export function Login() { const [email, setEmail] useState() const [password, setPassword] useState() const [error, setError] useState(null) const navigate useNavigate() async function handleSubmit(event) { event.preventDefault() setError(null) try { await login({ email, password }) navigate(/) } catch (error) { setError(error) } } return ( form onSubmit{handleSubmit} {error pError: {error.message}/p} input typeemail value{email} onChange{(e) setEmail(e.target.value)} placeholderEmail / input typepassword value{password} onChange{(e) setPassword(e.target.value)} placeholderPassword / button typesubmitLog In/button /form ) } // This will be shown when the user wants to sign up export function Signup() { const [email, setEmail] useState() const [password, setPassword] useState() const [error, setError] useState(null) const [needsConfirmation, setNeedsConfirmation] useState(false) async function handleSubmit(event) { event.preventDefault() setError(null) try { await signup({ email, password }) setNeedsConfirmation(true) } catch (error) { console.error(Error during signup:, error) setError(error) } } if (needsConfirmation) { return ( p Check your email for the confirmation link. If you dont see it, check spam/junk folder. /p ) } return ( form onSubmit{handleSubmit} {error pError: {error.message}/p} input typeemail value{email} onChange{(e) setEmail(e.target.value)} placeholderEmail / input typepassword value{password} onChange{(e) setPassword(e.target.value)} placeholderPassword / button typesubmitSign Up/button /form ) } // This will be shown has clicked on the link in their // email to verify their email address export function EmailVerification() { const [error, setError] useState(null) const navigate useNavigate() async function handleClick() { setError(null) try { // The token is passed as a query parameter const token new URLSearchParams(window.location.search).get(token) if (!token) throw new Error(Token not found in URL) await verifyEmail({ token }) navigate(/) } catch (error) { console.error(Error during email verification:, error) setError(error) } } return ( {error pError: {error.message}/p} button onClick{handleClick}Verify email/button / ) } // This will be shown when the user wants to reset their password export function RequestPasswordReset() { const [email, setEmail] useState() const [error, setError] useState(null) const [needsConfirmation, setNeedsConfirmation] useState(false) async function handleSubmit(event) { event.preventDefault() setError(null) try { await requestPasswordReset({ email }) setNeedsConfirmation(true) } catch (error) { console.error(Error during requesting reset:, error) setError(error) } } if (needsConfirmation) { return ( p Check your email for the confirmation link. If you dont see it, check spam/junk folder. /p ) } return ( form onSubmit{handleSubmit} {error pError: {error.message}/p} input typeemail value{email} onChange{(e) setEmail(e.target.value)} placeholderEmail / button typesubmitSend password reset/button /form ) } // This will be shown when the user clicks on the link in their // email to reset their password export function PasswordReset() { const [error, setError] useState(null) const [newPassword, setNewPassword] useState() const navigate useNavigate() async function handleSubmit(event) { event.preventDefault() setError(null) try { // The token is passed as a query parameter const token new URLSearchParams(window.location.search).get(token) if (!token) throw new Error(Token not found in URL) await resetPassword({ token, password: newPassword }) navigate(/) } catch (error) { console.error(Error during password reset:, error) setError(error) } } return ( form onSubmit{handleSubmit} {error pError: {error.message}/p} input typepassword autoCompletenew-password value{newPassword} onChange{(e) setNewPassword(e.target.value)} placeholderNew password / button typesubmitReset password/button /form ) }TypeScript 版本src/pages/auth.tsximport { login, requestPasswordReset, resetPassword, signup, verifyEmail, } from wasp/client/auth import { useState } from react import { useNavigate } from react-router-dom // This will be shown when the user wants to log in export function Login() { const [email, setEmail] useState() const [password, setPassword] useState() const [error, setError] useStateError | null(null) const navigate useNavigate() async function handleSubmit(event: React.FormEventHTMLFormElement) { event.preventDefault() setError(null) try { await login({ email, password }) navigate(/) } catch (error: unknown) { setError(error as Error) } } return ( form onSubmit{handleSubmit} {error pError: {error.message}/p} input typeemail value{email} onChange{(e) setEmail(e.target.value)} placeholderEmail / input typepassword value{password} onChange{(e) setPassword(e.target.value)} placeholderPassword / button typesubmitLog In/button /form ) } // This will be shown when the user wants to sign up export function Signup() { const [email, setEmail] useState() const [password, setPassword] useState() const [error, setError] useStateError | null(null) const [needsConfirmation, setNeedsConfirmation] useState(false) async function handleSubmit(event: React.FormEventHTMLFormElement) { event.preventDefault() setError(null) try { await signup({ email, password }) setNeedsConfirmation(true) } catch (error: unknown) { console.error(Error during signup:, error) setError(error as Error) } } if (needsConfirmation) { return ( p Check your email for the confirmation link. If you dont see it, check spam/junk folder. /p ) } return ( form onSubmit{handleSubmit} {error pError: {error.message}/p} input typeemail value{email} onChange{(e) setEmail(e.target.value)} placeholderEmail / input typepassword value{password} onChange{(e) setPassword(e.target.value)} placeholderPassword / button typesubmitSign Up/button /form ) } // This will be shown has clicked on the link in their // email to verify their email address export function EmailVerification() { const [error, setError] useStateError | null(null) const navigate useNavigate() async function handleClick() { setError(null) try { // The token is passed as a query parameter const token new URLSearchParams(window.location.search).get(token) if (!token) throw new Error(Token not found in URL) await verifyEmail({ token }) navigate(/) } catch (error: unknown) { console.error(Error during email verification:, error) setError(error as Error) } } return ( {error pError: {error.message}/p} button onClick{handleClick}Verify email/button / ) } // This will be shown when the user wants to reset their password export function RequestPasswordReset() { const [email, setEmail] useState() const [error, setError] useStateError | null(null) const [needsConfirmation, setNeedsConfirmation] useState(false) async function handleSubmit(event: React.FormEventHTMLFormElement) { event.preventDefault() setError(null) try { await requestPasswordReset({ email }) setNeedsConfirmation(true) } catch (error: unknown) { console.error(Error during requesting reset:, error) setError(error as Error) } } if (needsConfirmation) { return ( p Check your email for the confirmation link. If you dont see it, check spam/junk folder. /p ) } return ( form onSubmit{handleSubmit} {error pError: {error.message}/p} input typeemail value{email} onChange{(e) setEmail(e.target.value)} placeholderEmail / button typesubmitSend password reset/button /form ) } // This will be shown when the user clicks on the link in their // email to reset their password export function PasswordReset() { const [error, setError] useStateError | null(null) const [newPassword, setNewPassword] useState() const navigate useNavigate() async function handleSubmit(event: React.FormEventHTMLFormElement) { event.preventDefault() setError(null) try { // The token is passed as a query parameter const token new URLSearchParams(window.location.search).get(token) if (!token) throw new Error(Token not found in URL) await resetPassword({ token, password: newPassword }) navigate(/) } catch (error: unknown) { console.error(Error during password reset:, error) setError(error as Error) } } return ( form onSubmit{handleSubmit} {error pError: {error.message}/p} input typepassword autoCompletenew-password value{newPassword} onChange{(e) setNewPassword(e.target.value)} placeholderNew password / button typesubmitReset password/button /form ) }示例代码要点解读登录成功必须跳转login()成功后 Wasp 已在浏览器建立了会话此时应立即用navigate(/)之类的跳转把用户带到应用主页。注册后不登录signup()返回后用户处于“待验证”状态示例通过needsConfirmation状态切换到提示页面引导用户去邮箱点击确认链接。token 来自 URL 查询参数验证邮件与重置密码邮件中的链接会携带token查询参数因此两个“点击邮件链接后到达”的组件都用new URLSearchParams(window.location.search).get(token)来读取并显式处理 token 缺失的情况。autoCompletenew-password重置密码输入框应设置该属性避免浏览器自动填充旧的登录凭据同时便于密码管理器正确识别。深入底层这些函数究竟做了什么自定义 UI 并不神秘——你调用的每个函数最终都会由 Wasp 生成器编译为一次对服务端 REST 接口的调用。以 login 的 SDK 实现 为例export async function login(data: { email: string; password: string }): Promisevoid { try { const { sessionId } await api.post({ loginPath }, { json: data, }).json(SessionResponseSchema); await initSession(sessionId); } catch (e) { throw handleApiError(e); } }模板中的{ loginPath }等占位符会在wasp build/wasp start时由代码生成器替换为真实的路由路径。几个值得注意的细节错误统一包装所有函数都通过handleApiError(e)把网络或服务端错误转换为统一的错误对象因此你的 UI 可以直接读取error.message展示给用户。会话初始化login()成功后SDK 拿到sessionId并调用initSession完成会话的持久化这是“登录成功”在客户端层面的本质。注册数据类型的可扩展性signup 的实现 中有一个条件类型EmailSignupData当你在 Wasp 配置中定义了额外的注册字段时生成器会在签名中并入UserEmailSignupFields使signup()自动接受这些扩展字段。默认情况下只保存email与password如需自定义注册流程参见 overview 文档中的 “Customizing the signup process”。返回值语义verifyEmail、requestPasswordReset、resetPassword、signup返回{ success: boolean }verifyEmail还可能带有reason字段而login返回void——判断登录成功与否靠的是是否抛异常。仓库中已有真实的自定义 UI 调用范例例如 examples/ask-the-documents 的主页 与 Layout 就导入了wasp/client/auth的认证函数来驱动自己的界面你可以直接阅读这些示例了解在完整应用中的组织方式。服务端视角验证与安全细节理解服务端行为有助于你在 UI 层做出正确的错误处理与状态反馈。相关模板位于 server 端 email provider 目录含login.ts、signup.ts、requestPasswordReset.ts、resetPassword.ts、verifyEmail.ts。邮箱验证JWT 状态翻转服务端 verifyEmail 的流程是从请求体中取出token→ 用validateJWT校验并解析出邮箱 → 按 providerId 找到 AuthIdentity → 把isEmailVerified置为true→ 触发onAfterEmailVerifiedHook如果你配置了该 hook。token 无效时抛出400并统一返回 “Email verification failed, invalid token” 而不泄露具体原因。密码重置先验 token再验密码服务端 resetPassword 有一段值得注意的安全设计源码注释也明确说明The token is validated before the password so that an unauthenticated caller with an invalid token cant learn the deployments password policy.即先校验 token再校验新密码的强度规则避免未携带有效 token 的攻击者通过接口探知你部署环境中的密码策略。此外它还有两个关键行为重置即验证重置密码成功时会把isEmailVerified一并置为true因为能通过邮件链接重置密码本身就证明了对邮箱的控制权。会话全失效修改密码后会调用invalidateAllSessionsForAuthId使该用户的所有既有会话失效防止拿到旧会话的人继续使用。API 参考wasp/client/auth五大函数以下为官方文档给出的完整 API 说明可直接作为自定义 UI 的接口契约。login()用于登录用户的 action。成功后务必做页面跳转例如跳转到应用主页。它接收一个参数data: object必填字段如下email: string必填password: string必填signup()用于注册用户并启动邮箱验证流程的 action。注册成功后用户不会被登录因为其邮箱仍需验证。它接收一个参数data: object必填字段如下email: string必填password: string必填默认情况下Wasp 只保存email和password字段。如果要在注册流程中加入额外字段请阅读 overview 文档中的 “Customizing the signup process”。verifyEmail()用于将邮箱标记为有效、将用户账户标记为激活的 action。成功后务必做页面跳转例如跳转到登录页。它接收一个参数data: object必填字段如下token: string必填—— 注册时生成的 token会以名为token的 URL 查询参数形式出现在验证链接中。requestPasswordReset()用于请求发送密码重置邮件的 action。该动作不会立即重置密码只是发送邮件。它接收一个参数data: object必填字段如下email: string必填resetPassword()用于确认密码重置并提供新密码的 action。成功后务必做页面跳转例如跳转到登录页。它接收一个参数data: object必填字段如下token: string必填—— 请求密码重置时生成的 token会以名为token的 URL 查询参数形式出现在重置链接中。password: string必填—— 用户的新密码。把组件接入路由五个组件就位后还需要在路由层把它们串起来。一个典型的做法是为验证邮件与重置邮件链接创建专门的路由例如/verify-email与/reset-password其余表单挂载在登录/注册页面下。由于示例组件都导出了具名函数你可以在自己的路由配置中按需引入Route path/login element{Login /} / Route path/signup element{Signup /} / Route path/verify-email element{EmailVerification /} / Route path/request-password-reset element{RequestPasswordReset /} / Route path/reset-password element{PasswordReset /} /这样验证邮件与重置邮件里的链接地址就能直接命中对应的组件组件内部再通过URLSearchParams解析token完成后续调用。小结自定义邮箱认证 UI 的本质并不复杂五个组件 五个来自wasp/client/auth的函数。真正需要把握的是每个函数在成功与失败时的语义差异——login成功即建会话、signup成功但需等待邮箱验证、verifyEmail/resetPassword成功后应当跳转登录页、requestPasswordReset只发信不重置。结合本文对 SDK 模板与服务端实现的剖析你可以放心地把认证流程完全纳入自己的设计体系同时仍然享受 Wasp 在会话管理、token 校验与安全性上提供的完整保障。延伸阅读Auth UI 预置组件说明Auth Overview默认校验规则与注册字段自定义邮箱认证配置email.md客户端认证函数 SDK 入口服务端邮箱认证实现login / signup / verifyEmail / resetPassword / requestPasswordReset真实示例ask-the-documents 中的自定义认证 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),仅供参考
返回列表