news 2026/9/17 11:55:19

Nhost nhost-js Auth 模块详解:Nhost 认证服务的 TypeScript 客户端实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nhost nhost-js Auth 模块详解:Nhost 认证服务的 TypeScript 客户端实战指南

Nhost nhost-js Auth 模块详解:Nhost 认证服务的 TypeScript 客户端实战指南

【免费下载链接】nhostThe Open Source Firebase Alternative with GraphQL.项目地址: https://gitcode.com/GitHub_Trending/nh/nhost

本篇基于 Nhost 仓库中的官方参考文档 auth.md,系统讲解@nhost/nhost-js的 Auth 模块:如何导入并创建认证客户端、55 个 API 方法的完整能力图谱(邮箱/密码、匿名、OTP、Passkey、OAuth2/OIDC、PAT 等)、FetchError错误处理机制,以及底层的 PKCE 工具与请求中间件链实现。读完本文,你可以直接在项目里落地 Nhost 的注册、登录、会话刷新、二次认证与令牌交换等完整认证流程。

模块定位与两种导入方式

Auth 模块是与 Nhost Auth 服务交互的核心模块,封装了用户注册/登录、会话管理、多因素认证(MFA)、WebAuthn 安全密钥、个人访问令牌(PAT)以及完整的 OAuth2/OpenID Connect 标准端点。源码位于 client.ts(约 5500 行的类型化 API 客户端),模块入口 index.ts 通过export * from './client'export * from './pkce'同时导出 API 客户端与 PKCE 工具函数。

有两种使用方式:

  1. 通过主 Nhost 客户端间接使用(推荐)nhost.auth属性会携带会话刷新、令牌挂载等中间件,适合绝大多数场景;
  2. 直接导入 auth 子模块:适合只需要调用认证 API、不依赖完整 SDK 组合的特定用例。

包 package.json 中通过exports字段声明了独立的子路径入口(./auth./fetch./session等),支持 ESM(import)与 CJS(require)双格式,运行时要求 Node.js >= 22:

// 方式一:通过子模块直接导入 import { createAPIClient } from "@nhost/nhost-js/auth"; // 方式二:通过主客户端使用(文档示例写法) import { createClient } from "@nhost/nhost-js";

创建客户端与首次注册调用

典型用法是通过createClient传入项目的subdomainregion,SDK 会据此构造 auth 服务的 baseURL(源码中由generateServiceUrl('auth', subdomain, region, authUrl)完成,见 nhost.ts),然后调用nhost.auth.signUpEmailPassword完成邮箱密码注册:

import { createClient } from "@nhost/nhost-js"; const nhost = createClient({ subdomain, region, }); await nhost.auth.signUpEmailPassword({ email, password, });

若需要绕过主客户端,auth 子模块还导出低层工厂函数createAPIClient,可指定任意 baseURL 并注入自定义中间件链:

function createAPIClient( baseURL: string, chainFunctions?: ChainFunction[], ): Client;

chainFunctions默认为空数组;每个ChainFunction的签名为(next: FetchFunction) => FetchFunction,用于拦截和改写请求/响应(日志、重试、自定义请求头等)。

错误处理:FetchError 与自动消息提取

这是文档中非常实用的一节,必须掌握。SDK 在大多数操作中,只要请求返回状态码 >= 300 或请求彻底失败(如网络错误),都会抛出FetchError<ErrorResponse>类型的错误:

import { createClient } from "@nhost/nhost-js"; import { FetchError } from "@nhost/nhost-js/fetch"; const nhost = createClient({ subdomain, region, }); try { await nhost.auth.signInEmailPassword({ email, password, }); } catch (err) { if (!(err instanceof FetchError)) { throw err; // Re-throw if it's not a FetchError } console.log("Error:", err); // Error: { // body: { // error: 'invalid-email-password', // message: 'Incorrect email or password', // status: 401 // }, // status: 401, // headers: { // 'content-length': '88', // 'content-type': 'application/json', // date: 'Mon, 12 May 2025 08:08:28 GMT' // } // } // error handling... }

FetchError的三个关键成员在 fetch.ts 中定义:

成员类型说明
bodyT(此处为ErrorResponse原始响应体
statusnumberHTTP 状态码
headersHeaders响应头

由于FetchError extends Errorerr.message已被自动提取为人类可读文案。提取逻辑见 extractMessage:它依次尝试message字段、字符串型error字段、嵌套的error.message对象,以及 GraphQL 风格的errors[].message数组(多条以逗号拼接),全部不匹配时回退为"An unexpected error occurred"。因此只关心文案时,按标准Error处理即可:

try { await nhost.auth.signInEmailPassword({ email, password, }); } catch (err) { if (!(err instanceof Error)) { throw err; // Re-throw if it's not an Error } console.log("Error:", err.message); // Error: Incorrect email or password }

对应的ErrorResponse结构包含error(错误码字符串)、message(描述)与status(状态码)三个字段,例如上例中的invalid-email-password/ 401。

Client 方法全景:按能力域梳理

文档中Client接口共列出 55+ 个方法,全部返回Promise<FetchResponse<T>>(少数重定向类方法返回 URL 字符串)。FetchResponse<T>统一携带bodystatusheaders三个字段(fetch.ts)。以下按能力域完整梳理,签名与返回值均以文档为准。

账号登录(Sign-in)

方法签名(简化)说明
signInEmailPassword(body: SignInEmailPasswordRequest, options?) => Promise<FetchResponse<SignInEmailPasswordResponse>>邮箱 + 密码登录;启用 TOTP MFA 时返回 MFA challenge 而非直接会话
signInAnonymous(body?: SignInAnonymousRequest, options?) => Promise<FetchResponse<SessionPayload>>匿名登录,总是创建新用户;不受AUTH_DISABLE_AUTO_SIGNUP门控,而是由AUTH_DISABLE_SIGNUPAUTH_ANONYMOUS_USERS_ENABLED控制
signInIdToken(body: SignInIdTokenRequest, options?) => Promise<FetchResponse<SessionPayload>>使用 Apple/Google ID token 登录;用户不存在且未设置AUTH_DISABLE_AUTO_SIGNUP时自动注册
signInOTPEmail(body: SignInOTPEmailRequest, options?) => Promise<FetchResponse<"OK">>发起邮箱 OTP 登录,向邮箱发送一次性密码
signInPasswordlessEmail(body: SignInPasswordlessEmailRequest, options?) => Promise<FetchResponse<"OK">>魔法链接(magic link)登录
signInPasswordlessSms(body: SignInPasswordlessSmsRequest, options?) => Promise<FetchResponse<"OK">>手机号免密登录,发送 SMS OTP
signInPAT(body: SignInPATRequest, options?) => Promise<...>用 Personal Access Token 登录(自动化系统)
signInProviderURL返回string构造 OAuth2 第三方登录的授权跳转 URL
signInWebauthn(body: SignInWebauthnRequest, options?) => Promise<...>发起 Passkey/WebAuthn 登录,返回 challenge

注意signInIdTokensignInOTPEmailsignInPasswordlessEmail等方法文档中均明确写道:当服务端启用AUTH_DISABLE_AUTO_SIGNUP后,未注册用户必须先走对应的/signup/...端点注册,这是配置 Auth 服务时需要了解的重要行为分叉。

账号注册(Sign-up)

方法说明
signUpEmailPassword(body: SignUpEmailPasswordRequest, options?)邮箱密码注册,返回SessionPayload
signUpIdToken(body: SignUpIdTokenRequest, options?)Apple/Google ID token 注册
signUpOTPEmail(body: SignUpOTPEmailRequest, options?)邮箱 OTP 注册
signUpPasswordlessEmail(body: SignUpPasswordlessEmailRequest, options?)魔法链接注册
signUpPasswordlessSms(body: SignUpPasswordlessSmsRequest, options?)手机号免密注册
signUpProviderURL(params, options?)构造第三方登录的注册跳转 URL
signUpWebauthn(body: SignUpWebauthnRequest, options?)发起 Passkey 注册,返回PublicKeyCredentialCreationOptions

会话、令牌与验证

方法说明
refreshToken(body: RefreshTokenRequest, options?)用刷新令牌换新 JWT;旧刷新令牌被吊销并颁发新的(轮换机制),返回Session
signOut(body: SignOutRequest, options?)登出/吊销会话
tokenExchange(body: TokenExchangeRequest, options?)PKCE 授权码换会话:code(重定向获得的授权码)+codeVerifier(43–128 字符)
verifyToken(body: VerifyTokenRequest, options?)校验访问令牌有效性
verifyTicketURL(params?)构造验证链接(如邮箱验证、密码重置)的跳转 URL
getJWKs(options?)获取 JWKS 公钥集,用于客户端验证 JWT 签名
verifySignInMfaTotp/verifySignInOTPEmail/verifySignInPasswordlessSms/verifySignInWebauthn/verifySignUpWebauthn/verifyElevateWebauthn/verifyAddSecurityKey/verifyChangeUserMfa/verifyChangeUserPhoneNumber各验证流程的"第二步":提交 TOTP 码、邮箱 OTP、SMS OTP、WebAuthn 断言等完成验证

用户信息变更(需 elevated 权限)

方法说明
getUser(options?)获取当前用户资料(角色、元数据、账号状态),返回User
changeUserEmail(body: UserEmailChangeRequest, options?)修改邮箱,向新邮箱发验证邮件
changeUserPassword(body: UserPasswordRequest, options?)修改密码;该操作会原子地吊销包括当前请求在内的一切会话,客户端必须视为已登出并重新登录
changeUserPhoneNumber(body: UserPhoneNumberChangeRequest, options?)修改手机号,向新号码发 SMS OTP,验证通过前原号码不变
changeUserMfa(options?)生成 TOTP 密钥(返回TotpGenerateResponse:QR 码图片 URL + 密钥明文),用于开启/切换 MFA
deanonymizeUser(body: UserDeanonymizeRequest, options?)匿名用户转正:补充邮箱(可选密码)凭据
deanonymizeUserSms(body: UserDeanonymizeSmsRequest, options?)匿名用户转正:补充手机号,后续通过/signin/passwordless/sms/otp完成验证
sendPasswordResetEmail(body: UserPasswordResetRequest, options?)发送密码重置邮件
sendVerificationEmail(body: UserEmailSendVerificationEmailRequest, options?)发送邮箱验证链接
linkIdToken(body: LinkIdTokenRequest, options?)用 ID token 把当前账号与外部 OAuth 提供方账号绑定

请求体中的约束值得注意(来源 client.ts):密码字段password/newPassword长度为 3–50 字符(见UserPasswordRequest);UserDeanonymizeRequestsignInMethod取值为'email-password' | 'passwordless';多个验证类请求(UserEmailChangeRequestSignUpWebauthnVerifyRequestUserDeanonymizeRequest等)都支持可选的codeChallenge字段——"PKCE code challenge (S256),提供且需要邮箱验证时,验证重定向中返回授权码而不是刷新令牌",模式为^[A-Za-z0-9_-]{43}$

WebAuthn / 安全密钥

方法说明
addSecurityKey(options?)初始化添加 WebAuthn 安全密钥,返回PublicKeyCredentialCreationOptions(challenge),需 elevated 权限
verifyAddSecurityKey(body, options?)提交CredentialCreationResponse完成密钥绑定
elevateWebauthn(options?)为已登录用户生成提权 challenge(PublicKeyCredentialRequestOptions
verifyElevateWebauthn(body, options?)完成提权

相关类型(如AuthenticatorAssertionResponseauthenticatorData/clientDataJSON/signature均为 Base64url 编码,AuthenticatorAttestationResponse额外含attestationObjectpublicKeyAlgorithmtransports等)用于对接浏览器的navigator.credentialsAPI。

PAT(Personal Access Token)

createPAT(body: CreatePATRequest, options?): Promise<FetchResponse<CreatePATResponse>>;

生成可用于自动化系统的长期令牌,可替代常规认证流程,需 elevated 权限。配合signInPAT使用。

OAuth2 / OpenID Connect 标准端点

Auth 服务自身也是一套完整的 OAuth2 授权服务器,SDK 完整暴露了 RFC 标准端点:

方法RFC / 说明
oauth2AuthorizeURL(params?)/oauth2AuthorizePostURL(body, options?)授权端点(GET/POST),返回跳转 URL 字符串
oauth2Token(body: OAuth2TokenRequest, options?)令牌端点:支持authorization_coderefresh_token两种 grant
oauth2Introspect(body, options?)RFC 7662 令牌自省
oauth2Revoke(body, options?)RFC 7009 令牌吊销
oauth2UserinfoGet/oauth2UserinfoPostUserInfo 端点(GET/POST)
oauth2Jwks(options?)OAuth2/OIDC 签名公钥 JWKS
getOpenIDConfiguration(options?)/getOAuthAuthorizationServer(options?)OpenID Provider Metadata 与授权服务器元数据(RFC 8414),两者内容相同
oauth2LoginGet(params?)/oauth2LoginPost(body, options?)供同意(consent)UI 调用的内部端点:获取授权请求详情 / 完成登录并回调授权码

OAuth2AuthorizeParams包含标准授权参数,OAuth2TokenRequestgrant_type'authorization_code' | 'refresh_token'枚举(见文档类型定义节)。

第三方 Provider 令牌

方法说明
getProviderTokens(provider: SignInProvider, options?)OAuth 回调后立即拉取提供方令牌(access token / refresh token / 过期信息);会话在数据库中被清除,必须紧接着回调调用,用户需自行安全存储(如 localStorage)
refreshProviderToken(provider, body, options?)用 refresh token 刷新提供方访问令牌,避免用户重复认证
linkIdToken(body, options?)见用户信息变更小节

服务诊断

healthCheckGet()(返回"OK")、healthCheckHead()(返回void)、getVersion()(返回GetVersionResponse200)用于探活与版本探测。

此外还有pushChainFunction(chainFunction: ChainFunction): void——向该客户端的 fetch 中间件链追加一个自定义中间件(见下文中间件链一节)。

核心数据结构速览

文档"Interfaces"一节定义了 60 余个类型,这里摘取最关键的几组(完整定义见 auth.md 与 client.ts):

会话三件套

  • Session:刷新令牌接口返回,包含新访问令牌、刷新令牌与用户信息;
  • SessionPayload:多数登录/注册接口返回;
  • SignInEmailPasswordResponse:邮箱密码登录专用返回,可能带 MFA challenge(MFAChallengePayload)。

用户模型User(client.ts#L1382-L1457):

export interface User { avatarUrl: string; // 头像 URL createdAt: string; // date-time defaultRole: string; // 默认授权角色,如 "user" displayName: string; email?: string; // email 格式 emailVerified: boolean; id: string; // UUID isAnonymous: boolean; locale: string; // 2-3 字符语言码 metadata: Record<string, unknown> | null; phoneNumber?: string; phoneNumberVerified: boolean; roles: string[]; // 如 ["user","customer"] activeMfaType?: string; // 当前启用的 MFA 类型 }

MFA 相关TotpGenerateResponseimageUrl(QR 码 data URL,形如"data:image/png;base64,...")与totpSecret(用于手动输入密钥);UserMfaRequestactiveMfaType'totp' | ''——传空字符串即关闭 MFA。

OAuth/OIDC 类型OAuth2DiscoveryResponseOAuth2TokenRequest/ResponseOAuth2IntrospectRequest/Response(含token_type_hint)、OAuth2RevokeRequestOAuth2UserinfoResponseProviderSession等;WebAuthn 类型PublicKeyCredentialCreationOptions/RequestOptionsAuthenticatorSelectionauthenticatorAttachmentresidentKeyuserVerification等)、AuthenticatorAttestationResponseAuthenticatorAssertionResponse;以及JWK/JWKSetErrorResponseOKResponse(字面量'OK')等。

PKCE 工具:generatePKCEPair 与实现细节

auth 子模块还导出三个 PKCE(Proof Key for Code Exchange,RFC 7636)工具函数,用于邮箱验证等"重定向中携带授权码"的场景:

function generateCodeVerifier(): string; // 43 个 base64url 字符的随机 verifier function generateCodeChallenge(verifier: string): Promise<string>; // 由 verifier 派生 S256 challenge function generatePKCEPair(): Promise<{ verifier: string; challenge: string }>;

实现见 pkce.ts:generateCodeVerifiercrypto.getRandomValues生成 32 字节随机数后转为 base64url(替换+//并去掉尾部=),正好得到 43 字符,与codeChallenge字段的正则^[A-Za-z0-9_-]{43}$对齐;generateCodeChallengecrypto.subtle.digest('SHA-256', ...)对 verifier 做 S256 摘要再 base64url 编码。文件头注释说明其依赖 Web Crypto API,因此适用于浏览器、Node.js >= 19、Bun 与 Deno 等运行时。相关行为有测试覆盖:pkce.test.ts。

典型用法:注册时调用generatePKCEPair(),把challenge传给signUpEmailPassword/sendVerificationEmail等的codeChallenge字段,verifier保存在本地;验证重定向回来后携带授权码codeverifier调用tokenExchange换取会话。

中间件链与主客户端的会话管理

文档中Client的方法表里有pushChainFunction(),它背后是 createEnhancedFetch 实现的洋葱模型中间件:中间件数组通过reduceRight反向包裹原生fetch,按声明顺序执行,每个中间件都能在调用next前后分别拦截请求与响应。

主客户端工厂在 nhost.ts 中定义了三组开箱即用的配置函数,它们会把中间件同时挂到authstoragegraphqlfunctions四个客户端上:

  • withClientSideSessionMiddleware(L50-L69):客户端场景默认使用,链式为sessionRefreshMiddleware(令牌临近过期自动刷新)→updateSessionFromResponseMiddleware(响应带来新令牌时更新会话存储)→attachAccessTokenMiddleware(为所有服务请求挂载 Authorization 头)。createClient()会自动注入这一配置;
  • withServerSideSessionMiddleware:服务端场景,刻意去掉自动刷新中间件以避免并发请求下的竞态条件;createServerClient()必须显式传入storage(如基于 cookie 的实现);
  • withAdminSession(adminSession)/withChainFunctions(fns):前者用 admin secret 为 storage/graphql/functions 客户端附加特权会话(源码注释明确警告切勿用于客户端代码);后者向全部客户端追加自定义中间件。

会话本身由sessionStorage持久化,主客户端暴露getUserSession()refreshSession(marginSeconds = 60)(提前 N 秒刷新,传 0 强制刷新)与clearSession();浏览器下默认使用 localStorage,其他环境降级为内存存储,也可通过storage: new CookieStorage({...})等自定义后端替换(见 session/ 模块)。

文档与源码的对应关系

这份参考文档并非手写维护的散文,而是与源码严格同步的生成物:模块级说明直接引用了 docstrings.test.ts 中的测试代码片段({@includeCode ...}指令),auth.md里的 Usage 与 Error handling 示例即取自该测试文件;每个方法的签名、参数表与返回类型则由 OpenAPI 代码生成流程产出(gen.sh 驱动,devDependencies 含openapi3-ts)。因此当你发现文档与行为不一致时,优先核对 client.ts 的 JSDoc 与__tests__目录下的测试,它们是第一手依据。

实践要点小结

  • 常规应用用createClient({ subdomain, region })即可获得带自动会话管理的nhost.auth;只需认证 API 时可用@nhost/nhost-js/authcreateAPIClient(baseURL, chainFunctions)
  • 统一用instanceof FetchError分支错误处理:需要状态码/原始响应体看err.status/err.body,只需文案直接用err.message
  • 注意changeUserPassword会吊销全部会话,成功后必须重新登录;
  • 邮箱验证等重定向流程建议配合generatePKCEPair()+codeChallenge+tokenExchange,避免刷新令牌直接暴露在重定向 URL 中;
  • 涉及自动注册的行为受服务端环境变量影响:AUTH_DISABLE_AUTO_SIGNUPAUTH_DISABLE_SIGNUPAUTH_ANONYMOUS_USERS_ENABLED,部署前应在 Auth 服务配置中确认其取值;
  • 包要求 Node.js >= 22(package.jsonengines字段),PKCE 工具依赖 Web Crypto,Node 侧需 >= 19 的globalThis.crypto

【免费下载链接】nhostThe Open Source Firebase Alternative with GraphQL.项目地址: https://gitcode.com/GitHub_Trending/nh/nhost

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 11:51:46

IDEA社区版安装配置全流程:JDK环境到Java项目实战

1. 先想清楚再动手&#xff1a;社区版 IDEA 到底解决什么问题做 Java 开发这些年&#xff0c;被问得最多的一类问题不是“这段代码为什么报错”&#xff0c;而是“工具用哪个、怎么装”。Java 开发工具这条线上&#xff0c;IDEA 社区版是绕不开的一个选项&#xff0c;尤其对刚入…

作者头像 李华
网站建设 2026/9/17 11:51:36

rosdepc使用教程:告别ROS依赖安装超时与失败

开头直接从从业者视角切入&#xff0c;讲自己折腾ROS依赖管理的经历引出rosdepc。做ROS开发的人&#xff0c;十有八九都被rosdep折磨过。尤其是刚把系统装好、代码拉下来、准备编译工作空间的时候&#xff0c;一条rosdep install --from-paths src --ignore-src -r -y打下去&am…

作者头像 李华
网站建设 2026/9/17 11:50:57

TikTokDownloader TikTok作品下载失效?为什么无法下载与3步快速修复

TikTokDownloader TikTok作品下载失效&#xff1f;为什么无法下载与3步快速修复 【免费下载链接】TikTokDownloader 抖音 / TikTok 平台作品下载/数据采集工具 项目地址: https://gitcode.com/GitHub_Trending/ti/TikTokDownloader 你在 TikTokDownloader 里输入 TikTok…

作者头像 李华
网站建设 2026/9/17 11:50:57

DX12图形API渲染管线实战:25集从设备到贴图三角形

图形学和图形API这两个词&#xff0c;最近被聊得越来越多&#xff0c;但真正愿意沉下去把底层渲染管线摸透的人并不多。DX12就是那道绕不过去的坎——它把内存管理、同步、资源状态的活儿全甩给开发者&#xff0c;写起来比旧版图形API啰嗦十倍&#xff0c;可一旦跑通&#xff0…

作者头像 李华