跳转至正文
教程

在 React 中使用 Capacitor、Supabase 和社交登录

手把手教你使用 React 和 Capacitor 实现 Supabase 身份验证:包含 @capgo/capacitor-social-login. 原生 Google 与 Apple 登录、支持 nonce 安全的 signInWithIdToken,以及可直接发布到移动端的 Vite 应用。

在 React 中使用 Capacitor、Supabase 和社交登录

在应用中实现原生 Google 和 Apple 登录,并使用 React 作为后端支持,配合 Supabase Auth,并为相同的 UI 包裹 Capacitor 而无需处理移动端浏览器的重定向问题。 Capgo 社群登录 (@capgo/capacitor-social-login) 处理原生提供商的弹窗;你只需将提供商的 ID token 换取 Supabase session,即可通过 signInWithIdToken.

本文在现有的 Supabase + Social Login 指南基础上,增加了完整的 React + Vite 实操指南(包含项目布局、providers、routes 以及 Capacitor 配置)。关于提供商控制台和平台密钥,请继续参考 Supabase 集成文档 以及 Google + Supabase 通用配置指南。相关文章: 使用 Capacitor 实现 Google 登录, 使用 Capacitor 实现 Apple 登录,以及 Capacitor 中的身份验证选项.

目录

你将构建什么

  • 一套 Vite + React + TypeScript 包含登录按钮及已登录调试视图的 Web 应用
  • Supabase Auth 通过原生端创建的会话 Google (以及可选的 Apple)ID tokens
  • 同一个 bundle 运行在 Capacitor iOS 和 Android 上
  • 一个 nonce 安全的 Google 流程,与 插件示例应用

整体身份验证流程

使用 @capgo/capacitor-social-login,移动端 App 会执行 不会 依赖 signInWithOAuth WebView 内部的重定向。原生的 SDK 会返回一个 ID token;Supabase 会对其进行验证并签发自己的 JWT。

从 React UI 通过 Capgo Social Login 和 Google 到 Supabase Auth 的流程

当 Web 端,你仍然可以使用同一个插件(参见 Google Web 配置)。如果后续添加了浏览器回退方案,请在路由中处理所有的 OAuth 回调 URL。

基础 React (Vite) 应用

创建一个新应用(或复用现有的 React 代码库):

bun create vite capgo-supabase-auth --template react-ts
cd capgo-supabase-auth
bun install
bun add @supabase/supabase-js @capgo/capacitor-social-login react-router-dom @capacitor/core

启动开发服务器:

bun run dev

安装 Supabase 和 Social Login 后的本地 Vite 开发服务器

目标文件夹结构:

针对 Supabase 认证助手和 React 页面的建议 src 布局

在以下位置添加环境变量 .env.local (切勿提交密钥):

VITE_SUPABASE_URL=https://your-project-ref.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-public-key
VITE_GOOGLE_WEB_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
VITE_GOOGLE_IOS_CLIENT_ID=your-ios-client-id.apps.googleusercontent.com
VITE_APPLE_SERVICE_ID=com.yourapp.signin
VITE_SUPABASE_APPLE_CALLBACK_URL=https://your-project-ref.supabase.co/functions/v1/apple-signin-callback

为什么要将 Supabase 与 Social Login 结合使用?

Supabase 提供内置身份验证的强大 BaaS 服务,而 @capgo/capacitor-social-login plugin 可在 iOS、Android 和 Web 平台提供原生社交认证。若需更紧密的 Supabase 集成, @capgo/capacitor-supabase 提供了原生身份验证助手和 JWT 访问。它们共同实现了:

  • 无缝的社交身份验证
  • 安全的 Token 管理
  • 跨平台兼容性
  • 实时数据库集成
  • 内置的用户管理

前提条件

开始之前,请确保您已具备以下条件:

  • 配置好的 Capacitor 项目
  • Supabase 账号与项目
  • 所选社交供应商(Google、Apple、Facebook)的开发者账号

步骤 1:安装并配置 Social Login 插件

首先,安装 Capgo Social Login 插件:

bun add @capgo/capacitor-social-login

运行 bunx cap sync 之后 在 Capacitor 中添加原生平台 在 React 应用中添加 Capacitor。下文仅针对 Vite 的操作指南在执行该步骤前可在浏览器中运行。

请参阅 社交登录 以及 Supabase 插件页面以查看特定平台的配置方法。

步骤 2:配置 Supabase 项目

启用 Google ID Token 登录的 Supabase dashboard 检查清单

创建并配置您的 Supabase 项目

  1. 创建一个 Supabase 项目:

    • 前往 supabase.com 并注册/登录
    • 点击 “新建项目”
    • 选择您的组织
    • 输入 项目名称 (例如:“MyApp Auth”)
    • 设置 数据库密码 (请妥善保存)
    • 选择你的 区域 (选择离用户最近的)
    • 点击 “创建新项目”
  2. 获取项目凭据:

    • 创建完成后,前往 设置 > API
    • 复制你的 项目 URL (例如, https://your-project-ref.supabase.co)
    • 复制你的 anon public API 密钥
    • 保存这些信息,稍后在你的 app 中使用

配置身份验证设置

  1. 配置常规身份验证设置:

    • 前往 身份验证 > 设置
    • 在 常规设置:
      • 设置 站点 URL 至应用的 URL(例如: https://yourdomain.com 或 http://localhost:3000 用于开发环境)
      • 添加额外的 重定向 URL 如有需要:
        http://localhost:3000
        https://yourdomain.com
        capacitor://localhost (for mobile apps)
  2. 配置电子邮件设置 (可选但建议配置):

    • 在 SMTP 设置,配置您的邮件服务商
    • 这会启用邮件确认和密码重置
    • 开发时,您可以使用内置邮件服务

启用社交认证提供商

  1. 进入提供商区域:
    • 前往 Authentication > Providers 在您的 Supabase dashboard 中
    • 您将看到可用的社交提供商列表
    • 每个提供商都有一个 启用 切换与配置选项

现在让我们详细配置每个社交登录提供商:

步骤 3:在 Supabase 中配置社交提供商

在 Supabase 中设置 Google 身份验证

首先,获取您的 Google OAuth 凭据:

请参考我们的 Google 设置指南: Google 身份验证设置

本指南涵盖:

  • 创建 Google Cloud 项目
  • 为 Web、iOS 和 Android 设置 OAuth 2.0 凭据
  • 配置同意屏幕
  • 获取所需的 Client ID 和密钥

完成 Google 设置后,请在 Supabase 中进行配置:

按照 Supabase + Google 通用指南 查看各 dashboard 点击操作的截图。

  1. 在 Supabase 中启用 Google 提供商:
    • 在 Supabase dashboard 中,前往 Authentication > Providers
    • 找到 Google 并将其切换为 开启
    • 添加 Web 端, iOS,以及 Android 你在 Google Cloud 中创建的 client IDs(跳过你不发布的平台)
    • 点击 保存

本 React 教程的重要提示: 当你使用 signInWithIdToken 配合 @capgo/capacitor-social-login你确实需要 不会 需要 OAuth 客户端密钥 或 Google 的回调 URL 字段。请注册 Supabase 在验证各平台 Token 时将接受的所有 Client ID。

在 Supabase 中配置 Apple Authentication

获取 Apple 凭据:

请参考我们的 Apple 配置详细指南: Apple Authentication 配置

本指南涵盖:

  • 配置您的 Apple Developer 账号
  • 创建 App IDs 和 Service IDs
  • 配置 Sign in with Apple 能力
  • 生成所需的私钥
  • iOS、Android 及 Web 平台的特定配置

完成 Apple 端配置后,请在 Supabase 中进行如下设置:

  1. 在 Supabase 中启用 Apple 提供商:
    • 前往 Authentication > Providers 并开启 Apple 开启
    • 填写配置信息:
      • 客户端 ID:填写您的 iOS App ID;若同时发布两个平台,请填写 Apple Service ID,并用逗号分隔(例如 com.yourapp,com.yourapp.signin)。iOS 令牌使用 App ID 作为 audience;Android 和 Web 则使用 Service ID。详情请参阅 Supabase Apple 通用配置.
      • 在本教程的 signInWithIdToken 流程中, @capgo/capacitor-social-login你确实需要 不会 在 Apple 提供商表单中填写 OAuth client secret 或 Supabase OAuth callback URL。
    • 点击 保存

注意:由于 Apple 对 Service IDs、私钥和各平台特定的重定向 URL 有严格要求,Apple 身份验证的设置最为复杂。请务必按平台仔细阅读我们的文档。

在 Supabase 中设置 Facebook 身份验证

获取 Facebook 凭据:

请参考我们的 Facebook 完整配置指南: Facebook 身份验证配置

本指南涵盖:

  • 创建 Facebook 开发者账号和应用
  • 添加 Facebook Login 产品
  • 配置 OAuth 重定向 URI
  • 获取 App ID、App Secret 和 Client Token
  • iOS 和 Android 的平台专属配置

完成 Facebook 配置后,请在 Supabase 中进行如下设置:

  1. 在 Supabase 中启用 Facebook 提供商:
    • 前往 Authentication > Providers 并切换 Facebook 开启
    • 填写配置信息:
      • 客户端 ID: 您的 Facebook App ID
      • 客户端密钥 (Client Secret): 您的 Facebook App Secret
      • 重定向 URL: https://your-project-ref.supabase.co/auth/v1/callback (自动填充)
    • 点击 “保存”

重要提示:请务必将 Supabase 回调 URL (https://your-project-ref.supabase.co/auth/v1/callback) 添加到您的 Facebook 应用的 有效 OAuth 重定向 URI 中(位于 Facebook Login 设置内)。

Supabase 配置重要注意事项

行级安全性 (RLS):

  • 配置完身份验证后,请在您的数据表中启用 RLS
  • 前往 Database > Tables 并切换 启用 RLS 针对每张表
  • 创建策略,根据已认证用户控制数据访问权限

用户管理:

  • 在以下位置查看已认证用户 Authentication > Users
  • 在以下位置监控认证事件 Authentication > Logs
  • 在以下位置设置邮件模板 Authentication > Email Templates

测试配置:

  • 使用 Supabase 内置的认证测试工具
  • 前往 Authentication > Users 并点击 “邀请用户” 以测试邮件流程
  • 查看 日志 部分,查看是否有身份验证错误

第 4 步:配置 Social Login 插件

Supabase 配置好后,你需要用对应的凭据设置 Social Login 插件。 重要提示:该插件需要 OAuth 凭据,来自 原始提供商 (并非来自 Supabase),而 Supabase 负责服务端身份验证。

身份验证流程详解

在开始配置之前,先了解一下流程:

  1. 插件进行身份验证 通过社交登录提供商(Google/Apple/Facebook)原生完成认证
  2. 插件接收 token (access token、ID token)由提供商返回
  3. 您的应用发送这些 token 通过以下方式发送至 Supabase signInWithIdToken()
  4. Supabase 进行验证 向提供商验证 token 并创建用户会话
  5. Supabase 返回 用于应用身份验证请求的专属 JWT token

Google Plugin 配置

该插件需要您的 Web Client ID 用于所有平台,并可选择提供 iOS Client ID 以支持 iOS 特有功能:

import { SocialLogin } from '@capgo/capacitor-social-login';

await SocialLogin.initialize({
  google: {
    // Use the same Web Client ID you configured in Supabase
    webClientId: 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com',
    
    // Optional: iOS Client ID for iOS-specific features
    iOSClientId: 'YOUR_IOS_CLIENT_ID.apps.googleusercontent.com',
    
    // Use online mode so the plugin returns an idToken for Supabase
    mode: 'online'
  }
});

Google 配置要点:

  • 使用 Web Client ID (并非 Android/iOS client IDs) 用于 webClientId 字段
  • 只需 Web Client ID,该插件即可在所有平台上运行
  • 本指南中介绍的 iOSClientId 是可选的,仅用于 iOS 特有的 Google 功能

Apple 插件配置

Apple 配置在 iOS 和 Android 之间有所不同:

针对 iOS (原生 Apple Sign-In):

await SocialLogin.initialize({
  apple: {
    // No additional configuration needed for iOS
    // The plugin uses the native Apple Sign-In capability
  }
});

针对 Android/Web (需要 Service ID)。使用与以下相同的重定向规则: buildApplePluginConfig() React 教程中的规则:

import { Capacitor } from '@capacitor/core';

const platform = Capacitor.getPlatform();

await SocialLogin.initialize({
  apple:
    platform === 'android'
      ? {
          clientId: 'YOUR_SERVICE_ID',
          redirectUrl:
            'https://your-project-ref.supabase.co/functions/v1/apple-signin-callback',
        }
      : {
          clientId: 'YOUR_SERVICE_ID',
          redirectUrl: window.location.href,
        },
});

Apple 配置要点:

  • iOS 使用原生 Sign in with Apple(无需额外配置)
  • Android 使用您的 Service ID 和 Supabase apple-signin-callback Edge Function URL
  • Web 使用您的 Service ID 和 window.location.href,这必须与 Apple Return URLs 一致

Facebook 插件配置

Facebook 需要您的 App ID 和 Client Token:

await SocialLogin.initialize({
  facebook: {
    appId: 'YOUR_FACEBOOK_APP_ID',        // From Facebook Developer Dashboard
    clientToken: 'YOUR_FACEBOOK_CLIENT_TOKEN', // From Facebook Developer Dashboard
    
    // Optional: Use Facebook Limited Login (for enhanced privacy)
    limitedLogin: false // See our Facebook setup guide for important Limited Login details
  }
});

Facebook 的关键点:

  • 使用您在 Supabase 中配置的相同 App ID
  • Client Token 可以在 Facebook App 的 Basic Settings 中找到
  • limitedLogin: true 启用 Facebook 以隐私为核心的 Limited Login 功能 (仅限 iOS)
  • 重要提示:请参阅我们的 Facebook 设置指南 获取有关 Limited Login 的详细信息(包括 ATT 注意事项)

在 React 应用启动时初始化一次 @capgo/capacitor-social-login

完成插件初始化

以下是同时初始化所有 provider 的方法:

import { SocialLogin } from '@capgo/capacitor-social-login';

export async function initializeSocialLogin() {
  await SocialLogin.initialize({
    google: {
      webClientId: 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com',
      mode: 'online'
    },
    facebook: {
      appId: 'YOUR_FACEBOOK_APP_ID',
      clientToken: 'YOUR_FACEBOOK_CLIENT_TOKEN',
    },
    apple: {
      // Prefer buildApplePluginConfig() from the React walkthrough.
      // Android: redirectUrl = Supabase apple-signin-callback Edge Function
      // Web: redirectUrl = window.location.href
    }
  });
}

注意事项:

  • 调用 initialize() 一次 在应用启动时,而不是每次登录前
  • 你只需要配置计划使用的 provider
  • 这里的凭据来自 原始提供商而非来自 Supabase
  • 请确保 Provider 凭据与你在 Supabase 中配置的内容一致

React Supabase 客户端与 Auth 层

如果尚未安装,请安装 Supabase client:

bun add @supabase/supabase-js

为整个 React 树创建一个单一 client:

// src/lib/supabaseClient.ts
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  auth: {
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
});

Google + Supabase 的 Nonce 辅助函数

Supabase 需要一个 原始 nonce 中的 signInWithIdToken 以及 SHA-256 摘要 并传递给 Google。该 Supabase Google 指南 文档记录了完整流程。最小辅助函数:

// src/auth/nonce.ts
export function getUrlSafeNonce(): string {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');
}

export async function sha256Hex(message: string): Promise<string> {
  const data = new TextEncoder().encode(message);
  const hashBuffer = await crypto.subtle.digest('SHA-256', data);
  return Array.from(new Uint8Array(hashBuffer), (b) => b.toString(16).padStart(2, '0')).join('');
}

export async function createGoogleNoncePair(): Promise<{ rawNonce: string; nonceDigest: string }> {
  const rawNonce = getUrlSafeNonce();
  const nonceDigest = await sha256Hex(rawNonce);
  return { rawNonce, nonceDigest };
}

在一个模块中集成 Social Login + Supabase(参考 示例 supabaseAuthUtils.ts):

// src/auth/socialLogin.ts
import { Capacitor } from '@capacitor/core';
import { SocialLogin } from '@capgo/capacitor-social-login';
import { supabase } from '../lib/supabaseClient';
import { createGoogleNoncePair } from './nonce';

export function isAppleLoginConfigured(): boolean {
  const platform = Capacitor.getPlatform();
  if (platform === 'ios') return true;
  const serviceId = import.meta.env.VITE_APPLE_SERVICE_ID;
  if (!serviceId) return false;
  if (platform === 'android') {
    return Boolean(import.meta.env.VITE_SUPABASE_APPLE_CALLBACK_URL);
  }
  if (platform === 'web') return true;
  return false;
}

function buildApplePluginConfig() {
  const platform = Capacitor.getPlatform();

  if (platform === 'ios') {
    return {};
  }

  if (platform === 'android') {
    return {
      clientId: import.meta.env.VITE_APPLE_SERVICE_ID,
      redirectUrl: import.meta.env.VITE_SUPABASE_APPLE_CALLBACK_URL,
    };
  }

  if (platform === 'web') {
    return {
      clientId: import.meta.env.VITE_APPLE_SERVICE_ID,
      redirectUrl: window.location.href,
    };
  }

  return {};
}

function isGoogleNonceMismatch(error: { message?: string }): boolean {
  return (error.message ?? '').toLowerCase().includes('nonce');
}

let initialization: Promise<void> | null = null;

export function ensureSocialLoginInitialized(): Promise<void> {
  if (!initialization) {
    const initOptions: Parameters<typeof SocialLogin.initialize>[0] = {
      google: {
        webClientId: import.meta.env.VITE_GOOGLE_WEB_CLIENT_ID,
        iOSClientId: import.meta.env.VITE_GOOGLE_IOS_CLIENT_ID,
        mode: 'online',
      },
    };

    if (isAppleLoginConfigured()) {
      initOptions.apple = buildApplePluginConfig();
    }

    initialization = SocialLogin.initialize(initOptions).catch((error) => {
      initialization = null;
      throw error;
    });
  }
  return initialization;
}

async function signInWithGoogleSupabaseOnce(allowNonceRetry: boolean) {
  await ensureSocialLoginInitialized();
  const { rawNonce, nonceDigest } = await createGoogleNoncePair();

  const result = await SocialLogin.login({
    provider: 'google',
    options: {
      scopes: ['email', 'profile'],
      nonce: nonceDigest,
    },
  });

  const googleResult = result.result;
  if (!googleResult || googleResult.responseType !== 'online' || !googleResult.idToken) {
    throw new Error('Google login did not return an ID token');
  }

  const { data, error } = await supabase.auth.signInWithIdToken({
    provider: 'google',
    token: googleResult.idToken,
    nonce: rawNonce,
  });

  if (error) {
    if (allowNonceRetry && isGoogleNonceMismatch(error)) {
      await SocialLogin.logout({ provider: 'google' });
      return signInWithGoogleSupabaseOnce(false);
    }
    throw error;
  }

  return data;
}

export async function signInWithGoogleSupabase() {
  return signInWithGoogleSupabaseOnce(true);
}

export async function signInWithAppleSupabase() {
  if (!isAppleLoginConfigured()) {
    throw new Error('Apple sign-in is not configured for this platform');
  }
  await ensureSocialLoginInitialized();
  const result = await SocialLogin.login({
    provider: 'apple',
    options: { scopes: ['email', 'name'] },
  });

  const appleIdToken = result.result?.idToken;
  if (!appleIdToken) throw new Error('Apple login did not return an idToken');

  const { data, error } = await supabase.auth.signInWithIdToken({
    provider: 'apple',
    token: appleIdToken,
  });

  if (error) throw error;
  return data;
}

export async function signOutEverywhere() {
  await supabase.auth.signOut();
  await Promise.all([
    SocialLogin.logout({ provider: 'google' }),
    SocialLogin.logout({ provider: 'apple' }),
  ]);
}

Apple initialize 按平台 (参见 Supabase Apple 通用配置, Android, Web 端):

  • iOS: buildApplePluginConfig() 返回 {}。原生 Sign in with Apple 使用你的 bundle ID。不需要 redirectUrl.
  • Android: clientId 是你的 Apple Service ID。 redirectUrl 是你的 Supabase Edge Function (apple-signin-callback)。部署该函数,将 Apple Return URL 设为该端点,并添加 app 的 deep link intent filter,让流程能返回 WebView (Android Supabase Apple 指南).
  • Web 端: clientId 是你的 Service ID。 redirectUrl 为 window.location.href,且必须与 Apple Developer Portal 中的 Return URL 匹配 (Web Supabase Apple 指南).

ensureSocialLoginInitialized() 添加 apple 仅在以下情况提供 provider: isAppleLoginConfigured() 为 true 时,因此仅限 Google 的配置不会传递空的 Android 或 Web Apple 凭据。

该插件返回 result.idToken 在所有平台上(AppleProviderResponse 中的 @capgo/capacitor-social-login)。当您发布多个平台时,请在 Supabase 中同时注册 iOS App ID 和 Service ID。

将会话状态暴露给组件:

// src/auth/useAuth.tsx
import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
} from 'react';
import type { Session } from '@supabase/supabase-js';
import { supabase } from '../lib/supabaseClient';
import { ensureSocialLoginInitialized } from './socialLogin';

type AuthContextValue = {
  session: Session | null;
  loading: boolean;
  initError: string | null;
  clearInitError: () => void;
};

const AuthContext = createContext<AuthContextValue | null>(null);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [session, setSession] = useState<Session | null>(null);
  const [loading, setLoading] = useState(true);
  const [initError, setInitError] = useState<string | null>(null);

  useEffect(() => {
    void ensureSocialLoginInitialized()
      .then(() => setInitError(null))
      .catch((error) => {
        setInitError(error instanceof Error ? error.message : 'Social login failed to initialize');
      });
    supabase.auth.getSession().then(({ data }) => {
      setSession(data.session);
      setLoading(false);
    });
    const { data: subscription } = supabase.auth.onAuthStateChange((_event, nextSession) => {
      setSession(nextSession);
    });
    return () => subscription.subscription.unsubscribe();
  }, []);

  const clearInitError = useCallback(() => setInitError(null), []);

  const value = useMemo(
    () => ({ session, loading, initError, clearInitError }),
    [session, loading, initError, clearInitError],
  );

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used inside AuthProvider');
  return ctx;
}

React 登录 UI 与受保护路由

采用移动端布局、包含 Google 和 Apple 按钮的登录界面示例

登录面板:

// src/components/LoginPanel.tsx
import { useState } from 'react';
import {
  isAppleLoginConfigured,
  signInWithAppleSupabase,
  signInWithGoogleSupabase,
} from '../auth/socialLogin';
import { useAuth } from '../auth/useAuth';

export function LoginPanel() {
  const { initError, clearInitError } = useAuth();
  const [error, setError] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);
  const showApple = isAppleLoginConfigured();

  const run = async (action: () => Promise<unknown>) => {
    setBusy(true);
    setError(null);
    try {
      await action();
      clearInitError();
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Sign-in failed');
    } finally {
      setBusy(false);
    }
  };

  return (
    <div>
      <button type="button" disabled={busy} onClick={() => run(signInWithGoogleSupabase)}>
        Continue with Google
      </button>
      {showApple ? (
        <button type="button" disabled={busy} onClick={() => run(signInWithAppleSupabase)}>
          Continue with Apple
        </button>
      ) : null}
      {initError ? <p role="alert">{initError}</p> : null}
      {error ? <p role="alert">{error}</p> : null}
    </div>
  );
}

会话调试视图(稍后请替换为您真实的账户 UI):

// src/components/SessionDebug.tsx
import { useEffect, useState } from 'react';
import type { User } from '@supabase/supabase-js';
import { supabase } from '../lib/supabaseClient';
import { signOutEverywhere } from '../auth/socialLogin';

export function SessionDebug() {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    supabase.auth.getUser().then(({ data }) => setUser(data.user));
  }, []);

  return (
    <div>
      <button type="button" onClick={() => signOutEverywhere()}>Sign out</button>
      <pre>{JSON.stringify(user, null, 2)}</pre>
    </div>
  );
}

React 调试面板中的已登录会话 JSON

配合 react-router-dom:

// src/App.tsx
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import { AuthProvider, useAuth } from './auth/useAuth';
import { LoginPanel } from './components/LoginPanel';
import { SessionDebug } from './components/SessionDebug';

function HomePage() {
  const { session, loading } = useAuth();
  if (loading) return <p>Loading…</p>;
  if (session) return <Navigate to="/account" replace />;
  return <LoginPanel />;
}

function AccountPage() {
  const { session, loading } = useAuth();
  if (loading) return <p>Loading…</p>;
  if (!session) return <Navigate to="/" replace />;
  return <SessionDebug />;
}

export default function App() {
  return (
    <AuthProvider>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/account" element={<AccountPage />} />
        </Routes>
      </BrowserRouter>
    </AuthProvider>
  );
}

Mount the app in main.tsx 照常操作。在身份提供商登录成功后, onAuthStateChange 更新 session 和 React Router 会将用户重定向至 /account.

可选:Facebook

Facebook 的流程相同 signInWithIdToken 使用来自插件的 access token 模式。请参阅 Facebook 配置 以及其中的 provider 代码块 步骤 4.

在 React 应用中添加 Capacitor

构建 Web bundle,然后添加原生项目:

bun run build
bun add @capacitor/core @capacitor/cli @capacitor/ios @capacitor/android
bunx cap init "Capgo Auth Demo" com.example.authdemo --web-dir dist
bunx cap add ios
bunx cap add android

Capacitor 托管 Vite 构建,而原生 SDK 处理社交登录

每次 Web 构建后进行同步:

bun run build
bunx cap sync

准备好在设备上进行测试时,请打开原生 IDE:

bunx cap open ios
bunx cap open android

在硬件上测试前,请完成 Google 和 Apple 平台的配置步骤:

在 WebView 中调用的 supabase.auth.signInWithOAuth 教程通常需要 通用链接 (Universal Links) 或 应用链接 这样 Google 重定向后,系统会把控制权交还给 App。Native Google 登录使用 ID token 可以避免该重定向循环,因为该 plugin 根本不会打开浏览器进行 OAuth 往返流程。 Android 上的 Apple 登录 则有所不同:它仍然使用 Edge Function 重定向和 App Deep Link intent filter,让 WebView 在 Apple 登录后恢复运行。

你仍然可以为其他功能配置 deep links(例如密码重置邮件、magic links、营销 URL)。把相同的路径添加到 身份验证 > URL 配置 在 Supabase 中以及您的 apple-app-site-association / Android intent filters 中添加这些流程时。

第 8 步:特定平台配置

iOS 配置

有关 iOS 设置的详细说明,请参阅我们的特定平台指南:

快速摘要 - 添加到 ios/App/App/Info.plist:

<!-- Google Sign-In URL scheme -->
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>YOUR_REVERSED_CLIENT_ID</string>
    </array>
  </dict>
</array>

<!-- Apple Sign-In capability -->
<key>com.apple.developer.applesignin</key>
<array>
  <string>Default</string>
</array>

请参考相关指南获取完整的 iOS 设置说明,包括 Xcode 项目配置。

Android 配置

如需详细的 Android 设置说明,请参阅各平台专属指南:

Android 核心配置:

1. 获取 SHA-1 指纹 (Google 登录必填):

# For debug builds (development)
cd android
./gradlew signingReport

# Look for the SHA1 fingerprint under "Variant: debug"
# Add this SHA1 to your Google Cloud Console Android OAuth client

2. 配置 AndroidManifest.xml - 添加到 android/app/src/main/AndroidManifest.xml:

<!-- Internet permission -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Facebook configuration -->
<meta-data 
  android:name="com.facebook.sdk.ApplicationId" 
  android:value="@string/facebook_app_id"/>
<meta-data 
  android:name="com.facebook.sdk.ClientToken" 
  android:value="@string/facebook_client_token"/>

3. 添加 Facebook 资源 改为 android/app/src/main/res/values/strings.xml:

<string name="facebook_app_id">YOUR_FACEBOOK_APP_ID</string>
<string name="facebook_client_token">YOUR_FACEBOOK_CLIENT_TOKEN</string>

请按照文中链接的平台指南完成完整的 Android 配置,包括 Google Play Services 设置和包名配置。

第 9 步:已认证用户使用 Supabase 数据库

用户通过身份验证后,就可以使用带行级安全性 (RLS) 的 Supabase 数据库:

// Example: Fetch user profile
async function getUserProfile() {
  const { data: user } = await supabase.auth.getUser();
  
  if (user) {
    const { data, error } = await supabase
      .from('profiles')
      .select('*')
      .eq('id', user.user.id)
      .single();
    
    return data;
  }
}

// Example: Update user profile
async function updateUserProfile(updates: any) {
  const { data: user } = await supabase.auth.getUser();
  
  if (user) {
    const { data, error } = await supabase
      .from('profiles')
      .update(updates)
      .eq('id', user.user.id);
    
    return data;
  }
}

重要的安全注意事项

  1. 切勿泄露敏感密钥 在你的客户端 code 中
  2. 使用环境变量 进行配置
  3. 启用行级安全性 (RLS) 在 Supabase 中
  4. 验证 Token 如有需要,在后端进行验证
  5. 处理 Token 刷新 使用 Supabase 实现自动刷新

已知问题与故障排除

Google Nonce 不匹配 (尤其是 iOS)

如果 Supabase 返回 nonce 错误,可能是 Google 返回了缓存的 ID token。请通过插件退出 Google 登录,重试一次,并确认通过 nonceDigest 改为 SocialLogin.login 以及 rawNonce 改为 signInWithIdToken。该 示例应用 次重试后 SocialLogin.logout.

Token audience 错误

请在 Supabase 的 Google provider 中注册所有的 Google client ID(web、iOS、Android)。 aud ID token 中的 claim 必须与其中之一匹配。

OAuth 重定向教程 vs 本指南

使用以下内容的博客文章 signInWithOAuth 在 WebView 中,如果重定向打开的是 Safari 而不是你的 App,移动端可能会失败。建议优先使用原生登录 + signInWithIdToken 用于 Capacitor,或参考 Social Login Web 端配置 处理浏览器重定向。

各平台检查

Token 不匹配与网络错误

  • 在 Google Cloud、Supabase 和 ... 之间统一提供商凭据 SocialLogin.initialize
  • 在 React 中展示加载和错误状态(参见 LoginPanel 上文)

总结

你现在拥有了一套完整的身份验证系统,将 Supabase 强大的后端与原生社交登录能力结合在一起。该方案提供:

  • 安全的原生社交身份验证
  • 无缝 Token 管理
  • 实时数据库集成
  • 跨平台兼容性

Supabase 与 Capgo Social Login 插件的结合,为你的 Capacitor 应用提供了强大且可扩展的身份验证解决方案。

如需了解更多高级功能,例如 多因素认证 (MFA) 或 自定义 claims,请参阅 Supabase 文档 以及 Social Login 插件文档.

继续阅读:使用 Capacitor Social Login 插件设置 Supabase Authentication

如果你正在使用 使用 Capacitor Social Login 插件设置 Supabase Authentication 用来规划身份验证和账户流程,并将它与 @capgo/capacitor-social-login 在 @capgo/capacitor-social-login 中查看实现细节, @capgo/capacitor-passkey 有关 @capgo/capacitor-passkey 的实现细节, @capgo/capacitor-native-biometric 有关 @capgo/capacitor-native-biometric 的实现细节, 双因素认证 有关双因素认证的实现细节,以及 SSO (企业版) 有关 SSO (Enterprise) 的实现细节。

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。