Wasp 用户名密码认证从零搭建自定义登录注册 UIwasp/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/waspWasp 的 Username Password 认证默认提供开箱即用的 Auth UI 组件但当你需要完全掌控登录注册页面的视觉与交互时可以放弃生成组件直接在自己编写的 React 代码中调用 Wasp 暴露的login()与signup()动作——这正是 create-your-own-ui.md 讲解的核心内容。本文以该文档为骨架结合 Wasp 仓库中 SDK 模板的真实实现带你从零搭建一套自定义用户名密码登录注册 UI并深入理解wasp/client/auth暴露的动作 API、底层调用链与签名扩展机制。1. 为什么需要自定义 UI理解 Wasp 认证的两种使用方式Wasp 的认证架构把「后端认证逻辑」与「前端界面」彻底解耦。在 Auth 总览文档 中认证被划分为三种接入方式生成组件Auth UIWasp 直接生成LoginForm、SignupForm等现成组件最快可上线仅支持有限的自定义自定义 UICustom Auth UI你写自己的表单在提交时调用 Wasp 提供的认证动作函数。这是本文的主题它允许对外观和交互逻辑进行完全定制自定义认证动作Custom Auth Actions连后端 signup/login 逻辑都自己写属于高级用法不推荐应优先考虑 auth hooks。官方文档还特别强调两种 UI 方式不必二选一可以混合使用——例如登录用生成的组件、注册用自定义界面。「自定义 UI」之所以可行是因为 Wasp 在后端已经准备好了完整的 signup/login 端点与密码哈希、会话管理逻辑。所谓自定义本质上就是让客户端代码直接调用这些能力而「wasp/client/auth 下的login()/signup()就是这套能力的客户端入口」。2. 准备工作在 main.wasp 中启用用户名密码认证自定义 UI 的前提是后端已经启用usernameAndPassword认证方法。参考 examples/waspello/main.wasp.ts 等示例应用的写法在main.wasp.ts中配置import { app } from wasp.sh/spec export default app({ name: myApp, wasp: { version: {latestWaspVersion} }, title: My App, auth: { // 1. 指定用户实体在 schema.prisma 中定义 userEntity: User, methods: { // 2. 启用用户名密码认证 usernameAndPassword: {}, }, // 3. 未登录访问受保护页面时的重定向目标 onAuthFailedRedirectTo: /login, // 4. 登录/注册成功后的跳转地址默认 / onAuthSucceededRedirectTo: /, }, spec: [ route(LoginRoute, /login, page(LoginPage)), route(SignupRoute, /signup, page(SignupPage)), ], })同时保证schema.prisma中存在User模型至少包含id字段可自行扩展业务字段model User { id Int id default(autoincrement()) // Add your own fields below }配置完成后Wasp 的代码生成器会在构建产物中生成wasp/client/auth模块其中包含login()、signup()、logout()等客户端函数。模板源码位于 waspc/data/Generator/templates/sdk/wasp/auth/username/生成的完整 SDK 路径形如wasp/auth/username/actions/login.ts与signup.ts。注意本文面向 Wasp 0.19 版本线对应 web/versioned_docs/version-0.19 的文档体系采用的是main.wasp.ts写法。如果你在旧版 0.18 及更早使用main.waspWasp DSL 语法核心概念与wasp/client/auth的用法完全一致仅配置文件的写法不同。3. 自定义注册页调用 signup() 与 login()文档给出的注册页起点代码如下关键点在于handleSubmit中先signup()再login()因为signup()只创建账号、不会自动登录。import { login, signup } from wasp/client/auth import { useState } from react import { useNavigate } from react-router-dom export function Signup() { const [username, setUsername] 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 signup({ username, password }) await login({ username, password }) navigate(/) } catch (error: unknown) { setError(error as Error) } } return ( form onSubmit{handleSubmit} {error pError: {error.message}/p} input typetext autoCompleteusername value{username} onChange{(e) setUsername(e.target.value)} placeholderUsername / input typepassword value{password} onChange{(e) setPassword(e.target.value)} placeholderPassword / button typesubmitSign Up/button /form ) }几个值得注意的工程细节autoCompleteusername为浏览器密码管理器提供语义提示用户名输入框务必保留密码框使用typepassword避免明文展示失败时把error写入 state 并渲染error.message这是最简单的错误展示方式成功后的跳转必须自己实现上例用navigate(/)。因为 Wasp 内置 Auth UI 的自动跳转依赖onAuthSucceededRedirectTo配置而自定义 UI 中该配置不会自动生效——这正是官方 API 文档中反复强调的注意事项。JavaScript 版本的写法完全相同仅需去掉类型标注error用useState(null)、handleSubmit用无类型的event可参考文档原文的auth.jsx版本。4. 自定义登录页调用 login() 并自行处理跳转登录页与注册页几乎一致唯一区别是不需要调用signup()import { login } from wasp/client/auth import { useState } from react import { useNavigate } from react-router-dom export function Login() { const [username, setUsername] 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({ username, password }) navigate(/) } catch (error: unknown) { setError(error as Error) } } return ( form onSubmit{handleSubmit} {error pError: {error.message}/p} input typetext autoCompleteusername value{username} onChange{(e) setUsername(e.target.value)} placeholderUsername / input typepassword value{password} onChange{(e) setPassword(e.target.value)} placeholderPassword / button typesubmitLog in/button /form ) }之后将这两个组件挂到main.wasp.ts中声明的LoginRoute/SignupRoute路由上即可。为页面设置authRequired: true可以保护业务页面未登录访问会被重定向到onAuthFailedRedirectTo指定的路由详见 auth 总览文档。5. API 参考wasp/client/auth 导出的动作函数原文档为这两个函数提供了完整的 API 规格整理如下5.1login(data)用于登录用户的动作action。参数data: object必填包含username: string必填password: string必填注意使用暴露的login()时必须自己实现登录成功后的跳转逻辑例如重定向到首页Wasp 不会替你跳转。5.2signup(data)用于注册用户的动作action。该动作不会自动登录用户你仍需额外调用login()。参数data: object必填包含username: string必填password: string必填默认情况下Wasp 只会保存username和password字段。如需在注册流程中收集并持久化额外字段如地址、手机号请阅读 Auth 总览文档中“自定义注册流程”章节。5.3 从源码看 login/signup 的底层实现生成的客户端 SDK 模板waspc/data/Generator/templates/sdk/wasp/auth/username/actions/揭示了这两个函数背后的调用链// login.ts模板 export async function login(data: { username: string, password: string }): Promisevoid { try { const { sessionId } await api.post({ loginPath }, { json: data, }).json(SessionResponseSchema) await initSession(sessionId) } catch (error) { throw handleApiError(error) } }// signup.ts模板 type UsernameSignupData { username: string password: string } UserUsernameAndPasswordSignupFields export async function signup(data: UsernameSignupData): Promisevoid { try { await api.post({ signupPath }, { json: data, }) } catch (error) { throw handleApiError(error) } }可以推断出的关键事实signup()通过api.post向后端注册端点提交{ username, password }成功后不建立会话login()则从响应中取出sessionId再调用initSession把会话写入客户端存储localStorage。Wasp 的会话机制正是「客户端存localStorage、服务端存数据库」这一对偶结构参见 Auth 总览文档的 Sessions 章节若定义过userSignupFieldssignup()的入参类型会自动扩展为 UserUsernameAndPasswordSignupFields见下文第 6 节即额外注册字段天然被类型系统接管错误统一经handleApiError抛给调用方所以你在catch中拿到的error已经过 SDK 规整可直接读取error.message展示。值得一提的是Wasp 内置 Auth UI 的LoginSignupForm也是通过useUsernameAndPassword这个 hook 间接调用login/signup的见 waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/usernameAndPassword/useUsernameAndPassword.ts其提交逻辑与文档示例完全同构signup成功后再login成功后执行回调。换句话说文档给你的示例就是 Wasp 官方 Auth UI 的“翻版”你只需替换成自己的样式与交互。6. 扩展注册字段signup() 的入参不止 username 和 password原文档明确指出默认只保存username与password。如果自定义注册表单要收集额外字段例如address需要三步配合第一步在main.wasp.ts中为usernameAndPassword方法挂上userSignupFieldsauth: { userEntity: User, methods: { usernameAndPassword: { userSignupFields: import { userSignupFields } from src/auth/signup, }, }, onAuthFailedRedirectTo: /login, }第二步在schema.prisma的User模型中补充字段model User { id Int id default(autoincrement()) address String? }第三步在src/auth/signup.ts中定义字段校验逻辑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 }, })从模板源码 waspc/data/Generator/templates/sdk/wasp/auth/providers/types.ts 可以看到defineUserSignupFields与UserSignupFields的类型定义每个字段的值是一个「接收客户端数据、返回待入库字段值」的FieldGetter函数若数据非法则抛错。password被显式排除在自定义字段之外避免明文入库——它由 Wasp 认证后端统一做哈希处理。随后在你的自定义注册表单中直接把这些字段放进提交数据即可await signup({ username, password, address }) await login({ username, password }) navigate(/)由于signup()的入参类型会根据userSignupFields自动扩展见第 5.3 节模板中的 UserUsernameAndPasswordSignupFields多余字段会得到 TypeScript 的类型保护。若你用的是内置SignupForm而非自定义 UI则通过additionalFieldsprop 传递额外字段相关用法可参考 Auth UI 文档 与 Auth 总览文档。7. 默认校验规则你的表单需要对齐这些约束当使用默认认证流程时无论内置 Auth UI 还是直接调用官方提供的login/signup动作Wasp 会应用以下默认校验详见 Auth 总览文档的 Default Validations 章节username不能为空password不能为空、至少 8 个字符、且必须包含一个数字用户名以大小写不敏感的方式存储。自定义 UI 意味着前端校验完全由你掌控但后端仍会执行上述校验因此你的表单应在提交前做同样的检查避免无谓的网络往返。也可以使用任意校验库如 zod在userSignupFields中做更精细的字段级校验。8. 登录后的用户体验展示用户状态与登出自定义 UI 只接管登录/注册表单登录态展示与登出仍由 Wasp 的客户端 API 提供。将用户展示与自定义表单配合使用可以构建完整的认证体验在受保护页面中组件会通过userprop 拿到当前登录用户对象在未受保护的页面中使用useAuth()hook 获取用户内部是useQuery的薄封装返回相同格式数据使用logout动作登出import { logout } from wasp/client/auth const LogoutButton () { return button onClick{logout}Logout/button }典型用法摘自 Auth 总览文档import { useAuth, logout } from wasp/client/auth import { Link } from react-router-dom export function Main() { const { data: user } useAuth() if (!user) { return ( span Please Link to/loginlogin/Link or{ } Link to/signupsign up/Link. /span ) } else { return ( button onClick{logout}Logout/button / ) } }9. 实战完整示例一个可运行的自定义认证页面综合上述全部要点下面给出一个包含「注册 登录 额外字段 完整错误处理」的自定义认证页面可直接放入src/pages/auth.tsx使用路由声明见第 2 节import { login, signup } from wasp/client/auth import { useState } from react import { useNavigate } from react-router-dom export function Signup() { const [username, setUsername] useState() const [password, setPassword] useState() const [address, setAddress] useState() const [error, setError] useStateError | null(null) const navigate useNavigate() async function handleSubmit(event: React.FormEventHTMLFormElement) { event.preventDefault() setError(null) if (password.length 8 || !/\d/.test(password)) { setError(new Error(Password must be at least 8 characters long and contain a number)) return } try { await signup({ username, password, address }) await login({ username, password }) navigate(/) } catch (error: unknown) { setError(error as Error) } } return ( form onSubmit{handleSubmit} {error p style{{ color: red }}Error: {error.message}/p} input typetext autoCompleteusername value{username} onChange{(e) setUsername(e.target.value)} placeholderUsername / input typetext autoCompletestreet-address value{address} onChange{(e) setAddress(e.target.value)} placeholderAddress / input typepassword value{password} onChange{(e) setPassword(e.target.value)} placeholderPassword / button typesubmitSign Up/button /form ) }记住signup()不会自动登录所以成功注册后必须紧跟着调用login()而login()成功后不会自动跳转navigate(/)这类跳转逻辑必须写在自己的handleSubmit中。这两条规则就是「自定义 UI」与「内置 Auth UI」在行为上的核心差异也是本文全部代码示例的底层逻辑。10. 小结Wasp 把用户名密码认证的「后端能力」完整封装在wasp/client/auth中signup()负责创建用户可选附带userSignupFields定义的额外字段login()负责建立会话二者都遵循默认校验规则且成功后的跳转需要自行实现。从模板源码 login.ts 与 signup.ts 可以看出它们分别对应「注册端点请求」与「登录端点请求 会话初始化」而内置 Auth UI 的useUsernameAndPassword也只是它们的薄封装——因此自定义 UI 并不是「绕过框架」而是直接使用框架暴露的第一方 API。参考文档create-your-own-ui.md本文骨架、auth 总览、username-and-pass 总览、examples/waspello/main.wasp.ts真实配置示例。【免费下载链接】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),仅供参考