
Wasp 社交登录数据定制用 userSignupFields 与 configFn 覆盖 Provider 默认行为【免费下载链接】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用户在通过 Google、GitHub 等社交账号登录时后端会从 Provider 收到一份用户资料数据但 Wasp 默认不会将其写入数据库。本指南围绕 Wasp 提供的两个覆盖机制——userSignupFields与configFn——讲解如何在main.wasp中接入它们把 Provider 返回的displayName、邮箱等信息写入User实体并定制 OAuth 的客户端凭据与 scope。读完本文你将掌握默认行为是什么、两个覆盖函数的完整签名与调用方式、Google/GitHub 各自返回的数据结构以及它们在 Wasp 代码生成层与运行时 SDK 中的实际执行链路。默认行为为什么需要覆盖在main.wasp的app.auth.methods字典中加入google: {}或gitHub: {}即可启用对应的社交登录此时 Wasp 采用默认行为当用户首次通过社交账号登录时Wasp 会创建一个新用户账号并将其与所选 Provider 的账号关联供后续登录使用参见 web/versioned_docs/version-0.12/auth/social-auth/_default-behaviour.md。但默认情况下Wasp 不会存储任何从社交登录 Provider 收到的资料只会保存该用户在 Provider 侧的用户 ID如 Google 的sub、GitHub 的id。也就是说User实体里不会自动出现用户的昵称、头像、邮箱等字段。这一限制催生了 Wasp 提供的两个覆盖机制见 web/versioned_docs/version-0.12/auth/social-auth/_override-intro.mduserSignupFields定义在注册首次登录时如何从 Provider 返回的资料中提取字段并写入User实体configFn定制各 Provider 的 OAuth 配置例如客户端凭据与请求的 scope。下文以官方文档的经典示例为主线把两个机制放在同一个案例中完整走一遍。完整示例把 Provider 资料写入 User 实体官方文档web/versioned_docs/version-0.12/auth/social-auth/_override-example-intro.md的核心示例是当用户通过社交账号登录时后端会收到一份用户资料数据Wasp 允许你在userSignupFieldsgetter 内部访问这份数据。例如User实体可以包含一个displayName字段其值根据 Provider 返回的资料来设置同时用configFn定制 Provider 的配置。下面分别展示main.wasp与对应源码文件。1. 在 main.wasp 中声明两个覆盖项以 Google 为例在app.auth.methods.google字典下添加两个import声明web/versioned_docs/version-0.12/auth/social-auth/google.mdapp myApp { wasp: { version: ^0.11.0 }, title: My App, auth: { userEntity: User, methods: { google: { // highlight-next-line configFn: import { getConfig } from src/auth/google.js, // highlight-next-line userSignupFields: import { userSignupFields } from src/auth/google.js } }, onAuthFailedRedirectTo: /login }, } entity User {psl id Int id default(autoincrement()) username String unique displayName String psl} // ...userEntity: User告诉 Wasp 哪个实体代表用户关于该字段的详细说明见 社交登录总览。注意User实体中新增了displayName字段——它就是下面userSignupFields要写入的目标字段。2. 实现 userSignupFields 与 getConfig对应的源码文件放在src/auth/google.jsJavaScript 版本export const userSignupFields { username: () hardcoded-username, displayName: (data) data.profile.displayName, } export function getConfig() { return { clientID, // look up from env or elsewhere clientSecret, // look up from env or elsewhere scope: [profile, email], } }TypeScript 版本使用 Wasp 提供的defineUserSignupFields辅助函数来获得正确的类型提示web/versioned_docs/version-0.12/auth/social-auth/_getuserfields-type.mdimport { defineUserSignupFields } from wasp/server/auth export const userSignupFields defineUserSignupFields({ username: () hardcoded-username, displayName: (data) data.profile.displayName, }) export function getConfig() { return { clientID, // look up from env or elsewhere clientSecret, // look up from env or elsewhere scope: [profile, email], } }这里有两个值得注意的细节userSignupFields的键名必须与User实体的字段名一一对应每个键的值是一个 getter 函数接收从 Provider 拿到的数据对象返回要写入该字段的值。上面示例中displayName: (data) data.profile.displayName就是从 Provider 资料中取displayName字段username被硬编码为hardcoded-username说明 getter 不一定要从 Provider 数据取值也可以返回任意逻辑计算出的值——这为“用户注册后自定义用户名”之类的场景留出了空间。3. getter 的 data 参数从哪来getter 收到的data对象结构为{ profile: providerProfile }即 Provider 的原始资料被包裹在profile键下。这一点可以在 Wasp 生成的 OAuth 用户处理模板中得到印证在 oauth/user.ts 中注册流程会调用const userFields await validateAndGetUserFields( { profile: providerProfile }, userSignupFields, )而validateAndGetUserFields定义于 sdk/wasp/server/auth/utils.ts会遍历userSignupFields的每个字段把整个{ profile: providerProfile }对象传给对应的 getter收集返回值后再统一交给createUser落库for (const [field, getFieldValue] of Object.entries(userSignupFields)) { try { const value await getFieldValue(sanitizedData) result[field] value } catch (e) { throwValidationError(e.message) } }所以data.profile.displayName中的data就是这个{ profile: ... }对象profile下才是 Provider 返回的原始资料字段。configFn定制 Provider 的 OAuth 配置configFn的作用是返回一个包含Client ID、Client Secret 与 scope的对象用于定制 OAuth Provider 的配置API 参考见 google.md 的 API Reference 一节。export function getConfig() { return { clientID, // look up from env or elsewhere clientSecret, // look up from env or elsewhere scope: [profile, email], } }clientID/clientSecret在创建 Google OAuth 应用 / GitHub OAuth App 后获取一般从.env.server中的GOOGLE_CLIENT_ID、GOOGLE_CLIENT_SECRET或 GitHub 对应变量读取scope决定向 Provider 请求哪些数据。scope 直接决定userSignupFields的 getter 里能拿到哪些profile字段Google 默认只请求profilescope想拿到用户的邮箱必须在configFn中显式加上emailGitHub 默认不请求任何 scope只有显式声明user或user:emailscope 后Wasp 才会额外调用/user/emails端点并把邮箱列表合并进profile.emails。从代码生成层看configFn与userSignupFields的接入逻辑在 config/google.ts 模板中体现生成代码会先判断这两个ExtImport是否被定义未定义则置为undefined即使用默认行为定义后则把用户函数合并进 Provider 配置const _waspConfig: ProviderConfig { id: google.id, displayName: google.displayName, createRouter(provider) { const config mergeDefaultAndUserConfig({ scopes: { requiredScopes }, }, _waspUserDefinedConfigFn); // ... }, }在 GitHub 模板 config/github.ts 中还可以看到 scope 如何影响数据获取只有当config.scopes包含user或user:email时才会请求/user/emails端点并回填providerProfile.emails。各 Provider 的资料数据结构不同 Provider 返回的profile字段不同直接决定你能在 getter 里使用哪些数据。GoogleWasp 通过 Google 的/userinfo端点源码见 config/google.ts获取用户资料可能包含以下字段具体取决于请求的 scope[ name, given_name, family_name, email, email_verified, aud, exp, iat, iss, locale, picture, sub ]默认 scope 仅为profile需要邮箱时必须在configFn中追加emailscopesub是 Google 侧的用户唯一标识Wasp 用它作为providerUserId因此示例中displayName: (data) data.profile.displayName取的是name类字段Google 的userinfo中名为name如果你希望字段名更直观也可以写成data.profile.name。GitHubGitHub 的数据来自两个端点config/github.ts/user与/user/emails。/user端点返回类似{ login: octocat, id: 1, name: monalisa octocat, avatar_url: https://github.com/images/error/octocat_happy.gif, gravatar_id: }/user/emails端点返回邮箱数组[ { email: octocatgithub.com, verified: true, primary: true, visibility: public } ]注意只有在请求了user或user:emailscope 时两个端点的数据才会被合并邮箱会出现在 getter 收到的data.profile.emails中。GitHub 文档示例中的configFn因此返回scope: []默认不请求邮箱或scope: [user]需要邮箱时。运行时执行链路覆盖项在哪里生效把上述机制串起来一次社交登录的完整流程是参见 oauth/handler.ts 与 oauth/user.ts用户访问GET /auth/{provider}/loginWasp 生成并存储 OAuth state重定向到 Provider 的授权页Provider 回调/auth/{provider}/callbackWasp 校验 state、用授权码换取 access token调用getProviderInfo拉取用户资料providerProfile与providerUserId以{ providerName, providerUserId }为复合主键查询既有身份已存在直接触发onBeforeLoginHook/onAfterLoginHook并返回用户 ID不存在先触发onBeforeSignupHook然后执行validateAndGetUserFields({ profile: providerProfile }, userSignupFields)计算要写入User实体的字段最后createUser落库并触发onAfterSignupHook生成一次性 code 重定向回客户端客户端用其换取会话。可以看到userSignupFields的 getter 只在首次注册时执行已登录用户再次访问时不会重复写入。另外从 oauth/user.ts 的注释可以确认onBeforeSignupHook先于userSignupFieldsgetter 运行因此可以通过抛出异常来否决注册。进阶多步骤注册isSignupComplete 模式userSignupFields的另一个典型用法是自定义注册流程。官方文档在 社交登录总览 中给出三步方案以 Google 为例第 1 步给User实体加一个isSignupComplete布尔字段entity User {psl id Int id default(autoincrement()) username String? unique // highlight-next-line isSignupComplete Boolean default(false) psl}第 2 步在userSignupFields中把该字段固定为false表示“社交账号已创建但尚未完成补充注册”export const userSignupFields { isSignupComplete: () false, }第 3 步在客户端用useAuth()查询该标志并决定重定向目标import { useAuth } from wasp/client/auth import { Redirect } from react-router-dom export function HomePage() { const { data: user } useAuth() if (user.isSignupComplete false) { return Redirect to/edit-user-details / } // ... }文档同时指出对更复杂的注册流程只需把布尔值换成能容纳更多状态枚举的字段如currentSignupStep即可扩展同一思路。这展示了userSignupFields不止能映射 Provider 资料还能作为自定义业务状态注入User实体的入口。小结与注意事项默认不存资料不加任何覆盖时Wasp 只保存 Provider 侧的用户 ID 与本地用户 ID 的关联不写任何profile数据两个覆盖项各司其职userSignupFields决定“存什么、怎么算”configFn决定“能拿到什么”scope与“用谁的凭据”clientID/clientSecret键名即字段名userSignupFields的键必须与User实体字段匹配getter 返回undefined或抛错会导致校验失败validateAndGetUserFields会抛出throwValidationError数据可用性取决于 scopeGoogle 默认profileGitHub 默认无 scope需要邮箱等数据时务必在configFn.scope中显式声明只在注册时执行getter 仅在首次登录创建用户时运行不会在每次登录时更新User字段。若希望更深入地查看 provider 级 API 参考可直接阅读 google.md 与 github.md 的 API Reference 章节运行时 SDK 的类型定义与校验逻辑可进一步阅读 sdk/wasp/server/auth/utils.ts 中的validateAndGetUserFields、createUser等实现。【免费下载链接】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),仅供参考