认证

接入邮箱、Apple、Google、会话存储和账号管理。

认证基于 Supabase Auth 完整集成。应用支持邮箱密码、邮箱验证码登录、注册验证、忘记/重置密码、修改邮箱/密码、账号删除,以及原生 Apple + Google 登录。

运行时结构

lib/auth.tsx 中的 AuthProvider 是应用的认证状态源。它加载初始 Supabase session,订阅 onAuthStateChange,暴露 useAuth(),并集中封装认证方法,避免各个 screen 直接调用 Supabase Auth。

AuthGate 是 guest-first 的:它不会在启动时把整个应用挡在登录页后面。公开页面可以在未登录状态渲染,受保护区域使用 SignInGate。当已登录用户进入 auth stack 时,AuthGate 会把登录流程关闭,回到发起登录的页面或首页。

会话存储

lib/supabase.ts 创建 createClient<Database>(...) 时使用平台相关的持久化层:

平台存储原因
iOS / AndroidChunkedSecureStoreSupabase session 可能超过 SecureStore 单值实用限制,因此 lib/secure-store-chunked.ts 会把长值切成 1800 字符片段
Webwindow.localStorageWeb 构建使用浏览器存储,并关闭 URL session 检测

Auth token 应只保存在 Supabase client 的存储层。不要把 session 复制到 AsyncStorage、app preferences 或 analytics 属性中。

邮箱流程

Screen 位于 app/(auth)/,表单 UI 位于 components/auth/。校验使用 react-hook-formlib/auth-schemas.ts 中的 Zod schema。

流程代码路径Supabase 调用
密码登录SignInFormsignInsignInWithPassword
验证码登录SignInFormsendLoginOtp / verifyLoginOtpsignInWithOtp({ shouldCreateUser: false })verifyOtp(type: "email")
注册SignUpFormsignUpfull_name 用户 metadata 的 signUp
注册验证SignUpFormverifyEmailOtpverifyOtp(type: "email")
忘记密码forgot-password.tsxresetPasswordresetPasswordForEmail
完成重置reset-password.tsxcompletePasswordResetverifyOtp(type: "recovery")updateUser({ password })
修改邮箱account-security.tsxchangeEmail / verifyEmailChangeOtpupdateUser({ email })verifyOtp(type: "email_change")
修改密码account-security.tsxchangePasswordupdateUser({ password })

凭据提交和 OTP 验证不会重试,因为重试可能消耗一次性 token 或触发 provider 限流。幂等辅助调用使用 lib/network.ts 中的小型 retryAsync 包装。

Apple Sign-In

Apple 只支持原生 iOS。components/auth/social-connections.tsxPlatform.OS === "ios" 时懒加载 expo-apple-authentication,请求 full name 和 email scope,然后把返回的 identity token 传给:

supabase.auth.signInWithIdToken({
  provider: "apple",
  token: credential.identityToken,
  nonce: credential.nonce,
});

如果 Apple 在首次登录返回姓名字段,应用会更新 auth metadata,并 upsert profiles.full_name。在 Android 和 Web 上不会加载该模块,按钮会提示 Apple Sign-In 不可用。

设置清单:

  • 为 Apple app identifier 启用 Sign in with Apple。
  • 在 Supabase Auth 中启用 Apple provider。
  • capability 或 config-plugin 变更后重新构建原生应用。

Google Sign-In

Google 只支持原生 iOS/Android。应用懒加载 @react-native-google-signin/google-signin,用 EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID 和可选的 EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID 配置;Android 会在需要时检查 Play Services,然后把 Google ID token 传给 Supabase signInWithIdToken

创建 OAuth clients

创建 Google Cloud Web OAuth client,并添加这个 redirect URI:

https://<ref>.supabase.co/auth/v1/callback

为每个要构建的 bundle identifier 创建 iOS OAuth client:

expo.soarstarter.com.dev
expo.soarstarter.com.preview
expo.soarstarter.com

为每个 package name 和测试/发布签名证书 SHA-1 创建 Android OAuth client。

配置 Supabase

Authentication → Providers → Google 启用 Google,并设置:

Client IDs = <web-client-id>,<ios-client-id-1>,<ios-client-id-2>,...
Client Secret = <web-client-secret>
Skip nonce checks = ON

把 Web client ID 放在第一位,并加入所有 Supabase 需要接受其 token audience 的原生 client ID。

设置 Expo env

EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID=<web-client-id>.apps.googleusercontent.com
EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID=<ios-client-id>.apps.googleusercontent.com

env 变更后重启 Metro。原生 identifier 或 config-plugin 行为变更后重新构建 development client。

如果当前运行时不可用,Google 按钮会显示友好的 toast,而不是让应用崩溃。共享的社交按钮映射里有 GitHub,但目前只会显示“not yet available”的占位提示。

Account & Security

app/account-security.tsx 提供已登录账号操作:

  • 修改邮箱并验证新邮箱验证码。
  • 修改密码。
  • 登出其他 session。
  • 当原生 biometrics 可用时启用或关闭 biometric lock。
  • 通过 delete-account Edge Function 删除账号。

删除账号会用已登录 JWT 调用 supabase.functions.invoke("delete-account")。函数先删除用户 rows、移除 sample-uploads/<user-id>/ 下的文件,再删除 auth user。rows 和文件先删,是为了让中途失败时用户仍保持登录状态并可重试。subscriptionspayments 会从 auth.users 级联删除。

如果 iOS 应用允许用户创建账号,Apple 要求应用内提供账号删除流程。请保留这个入口。

未登录功能门

受保护但位于公开应用内的功能面使用 components/sign-in-gate.tsxSignInGate。它渲染本地化登录 CTA 并打开 /login,session 落地后 auth stack 会自动关闭。

验证认证

  • 使用邮箱密码注册,验证邮箱验证码,并确认 auth user 和 profiles row 存在。
  • 登出后分别用密码和邮箱验证码登录。
  • 跑通 forgot/reset password。
  • 在原生构建中,完成 provider 设置后测试 iOS Apple,以及 iOS/Android Google。
  • 删除测试账号,并确认 auth user、rows 和上传文件都被移除。

相关页面

On this page