네이티브 Google 및 Apple 로그인을 React 앱에 구현하고, 이를 Supabase Auth로 연동하세요, 그리고 동일한 UI를 감싸서 Capacitor 모바일 브라우저 리다이렉트 문제 없이 구현합니다. Capgo 소셜 로그인 (@capgo/capacitor-social-login) handles the native provider sheets; you exchange the provider ID token for a Supabase session with signInWithIdToken.
이 글에서는 기존의 Supabase + Social Login 가이드를 확장하여 React + Vite 전체 워크스루(프로젝트 레이아웃, 프로바이더, 라우트, Capacitor 연결)를 제공합니다. 프로바이더 대시보드 및 플랫폼 키 설정은 기존 Supabase 연동 문서 및 Google + Supabase 일반 설정를 계속 참고하세요. 관련 포스트: Capacitor를 이용한 Google 로그인, Capacitor를 이용한 Apple 로그인, 그리고 Capacitor의 인증 옵션.
목차
- 구현할 내용
- 전체 인증 흐름
- 기본 React (Vite) 애플리케이션
- Supabase 프로젝트 및 Google 프로바이더 설정
- Supabase에서 프로바이더 구성하기
- Social Login 플러그인 초기화
- Supabase 클라이언트 및 React 인증 레이어
- 로그인 UI 및 보호된 라우트
- React 앱에 Capacitor 추가
- 플랫폼 설정
- RLS를 통한 데이터베이스 액세스
- 알려진 이슈 및 트러블슈팅
- 보안 고려 사항
구현할 내용
- 어떤 Vite + React + TypeScript 로그인 버튼 및 로그인 후 디버그 뷰를 포함한 웹 앱
- Supabase Auth 네이티브에서 생성된 세션 Google (및 선택 사항인 Apple) ID 토큰)
- 동일한 번들이 실행되는 Capacitor iOS 및 Android 환경에서
- 보안이 강화된 Google 인증 흐름 플러그인 예제 앱
전체 인증 흐름
사용하면 @capgo/capacitor-social-login, 모바일 앱은 하지 마세요 의존하지 않습니다. signInWithOAuth 네이티브 SDK는 ID 토큰을반환하며, Supabase가 이를 검증해 자체 JWT를 발급합니다.

웹 환경에서도 동일한 플러그인을 사용할 수 있습니다 (다음 참조: Google 웹 설정). 나중에 브라우저 폴백을 추가할 경우, 라우터에서 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

대상 폴더 구조:

환경 변수 추가: .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
Social Login과 함께 Supabase를 사용하는 이유
Supabase는 내장 인증 기능을 갖춘 강력한 backend-as-a-service를 제공하며, @capgo/capacitor-social-login plugin은 iOS, Android, 웹 플랫폼 전반에서 네이티브 소셜 인증을 지원합니다. 더 긴밀한 Supabase 통합을 위해, @capgo/capacitor-supabase 네이티브 인증 헬퍼와 JWT 액세스를 추가합니다. 이를 통해 다음과 같은 기능을 제공합니다:
- 매끄러운 소셜 인증
- 안전한 토큰 관리
- 크로스 플랫폼 호환성
- 실시간 데이터베이스 통합
- 내장된 사용자 관리
사전 요구사항
시작하기 전에 다음 사항을 확인하세요:
- 설정된 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 프로젝트 설정

Supabase 프로젝트 생성 및 구성
-
Supabase 프로젝트 생성:
- 다음으로 이동: supabase.com 및 회원가입/로그인
- 클릭 “새 프로젝트”
- 조직 선택
- 입력: 프로젝트 이름 (예: “MyApp Auth”)
- 다음 설정: 데이터베이스 비밀번호 (안전하게 보관하세요)
- 다음 항목을 선택하세요: 리전 (사용자와 가장 가까운 곳 선택)
- 클릭 “새 프로젝트 만들기”
-
프로젝트 자격 증명 가져오기:
- 생성이 완료되면, Settings > API
- 복사할 항목 프로젝트 URL (예:
https://your-project-ref.supabase.co) - 복사할 항목 anon public API 키
- 앱에서 나중에 사용할 수 있도록 저장해 두세요
인증 설정 구성
-
일반 인증 설정 구성:
- 다음으로 이동: 인증 > 설정
- 아래 일반 설정:
- 설정 사이트 URL 앱 URL로 설정하세요 (예:
https://yourdomain.com또는http://localhost:3000개발용) - 추가 리다이렉트 URL 필요한 경우:
http://localhost:3000 https://yourdomain.com capacitor://localhost (for mobile apps)
- 설정 사이트 URL 앱 URL로 설정하세요 (예:
-
이메일 설정 구성 (선택 사항이지만 권장됨):
- 아래 SMTP 설정, 이메일 프로바이더를 설정하세요
- 이 설정을 통해 이메일 인증 및 비밀번호 재설정이 가능합니다
- 개발 단계에서는 내장 이메일 서비스를 사용할 수 있습니다
소셜 인증 프로바이더 활성화
- Providers 섹션으로 이동하여:
- 다음으로 이동: 인증 > 프로바이더 Supabase dashboard에서
- 사용 가능한 소셜 프로바이더 목록이 표시됩니다
- 각 프로바이더에는 활성화 토글 및 설정 옵션
이제 각 소셜 프로바이더를 자세히 설정해 보겠습니다:
3단계: Supabase에서 소셜 프로바이더 설정
Supabase에서 Google 인증 설정하기
먼저, Google OAuth 자격 증명을 가져오세요:
다음의 상세한 Google 설정 가이드를 참고하세요: Google 인증 설정
이 가이드의 내용:
- Google Cloud 프로젝트 생성하기
- 웹, iOS, Android용 OAuth 2.0 자격 증명 설정
- 동의 화면(Consent screen) 구성
- 필요한 Client ID 및 Secret 가져오기
Google 설정을 완료한 후 Supabase에서 구성하세요:
다음 Supabase + Google 일반 가이드 를 참고하여 각 dashboard 클릭 스크린샷을 확인하세요.
- Supabase에서 Google provider 활성화하기:
- Supabase dashboard에서 다음으로 이동하세요: 인증 > 프로바이더
- 다음 항목을 찾아 Google 스위치를 ON으로 전환하세요
- 다음을 추가하세요 웹, iOS, 그리고 Android Google Cloud에서 생성한 client ID (출시하지 않는 플랫폼은 건너뛰세요)
- 클릭 저장
이 React 튜토리얼에서 중요한 사항: 사용 시 signInWithIdToken 포함 @capgo/capacitor-social-login, 다음과 같이 합니다 하지 마세요 OAuth가 필요합니다 클라이언트 시크릿 또는 Google용 callback URL 필드가 필요합니다. 각 플랫폼의 토큰을 검증할 때 Supabase가 허용할 모든 client ID를 등록하세요.
Supabase에서 Apple Authentication 설정하기
Apple 인증 정보 가져오기:
상세한 Apple 설정 가이드를 확인하세요: Apple Authentication 설정
이 가이드의 내용:
- Apple Developer 계정 설정
- App ID와 Service ID 생성
- Sign in with Apple 기능 구성
- 필요한 private key 생성
- iOS, Android 및 Web 플랫폼별 설정
Apple 설정을 마친 후, Supabase에서 다음과 같이 구성하세요:
- Supabase에서 Apple provider 활성화:
- 다음으로 이동: 인증 > 프로바이더 및 Apple ON으로 전환하세요
- 설정 정보를 입력하세요:
- Client 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을 입력해야 합니다.
- Client ID: iOS App ID와 두 플랫폼 모두 배포 시 사용하는 Apple Service ID를 쉼표로 구분하여 입력하세요 (예:
- 클릭 저장
참고: Apple 인증 설정은 Service ID, private key, 플랫폼별 리다이렉트 URL 등 요구사항이 까다로워 가장 복잡합니다. 각 플랫폼별 문서를 주의 깊게 확인하세요.
Supabase에서 Facebook 인증 설정하기
Facebook 자격 증명 가져오기:
Facebook 설정 가이드를 확인하세요: Facebook 인증 설정
이 가이드의 내용:
- Facebook Developer 계정 및 앱 생성
- Facebook Login 제품 추가
- OAuth 리다이렉트 URI 구성
- App ID, App Secret, Client Token 확인
- iOS 및 Android 플랫폼별 설정
Facebook 설정 완료 후, Supabase에서 다음과 같이 구성하세요:
- Supabase에서 Facebook 프로바이더 활성화:
- 다음으로 이동: 인증 > 프로바이더 및 토글 Facebook ON으로 전환하세요
- 설정 정보를 입력하세요:
- Client ID: Facebook App ID
- 클라이언트 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 설정에 반드시 추가하세요.
Supabase 설정 시 주의사항
행 수준 보안 (RLS):
- 인증 설정 후, 테이블에서 RLS를 활성화하세요.
- 다음으로 이동: Database > Tables 에서 Enable RLS를 토글하세요. 각 테이블에 대해
- 인증된 사용자를 기준으로 데이터 접근을 제어하는 정책 생성
사용자 관리:
- 인증된 사용자 보기: Authentication > Users
- 인증 이벤트 모니터링: Authentication > Logs
- 이메일 템플릿 설정: Authentication > Email Templates
설정 테스트:
- Supabase의 내장 인증 테스트 도구 사용
- 다음으로 이동: Authentication > Users 그리고 클릭하세요: “사용자 초대” 이메일 흐름 테스트를 위해
- 앱 통합의 로그 인증 오류 확인 섹션
4단계: 소셜 로그인 플러그인 설정
Supabase 설정이 완료되었으므로, 이제 해당 자격 증명을 사용하여 Social Login 플러그인을 설정해야 합니다. 중요: 플러그인에 다음의 OAuth 자격 증명이 필요합니다: 기존 제공업체 (Supabase가 아닌) 플러그인이 처리하고, Supabase는 서버 측 인증을 담당합니다.
인증 흐름의 작동 방식
설정에 들어가기 전에 흐름을 먼저 이해하세요:
- 플러그인이 인증을 수행합니다 소셜 프로바이더(Google/Apple/Facebook)와 네이티브로
- 플러그인이 토큰을 받습니다 프로바이더로부터 (access token, ID token)을 받습니다
- 앱이 이 토큰들을 전송합니다 Supabase로 전송할 때 사용하는
signInWithIdToken() - Supabase가 검증합니다 프로바이더로 토큰을 검증하고 사용자 세션을 생성합니다
- Supabase가 반환합니다 앱의 인증된 요청을 위한 자체 JWT 토큰
Google 플러그인 설정
플러그인에 다음 정보가 필요합니다: 웹 클라이언트 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 설정 핵심 사항:
- 트레이드오프를 평가하려면 웹 클라이언트 ID (Android/iOS client ID가 아님) {@field} 항목에 입력할
webClientIdfield - Web Client ID만 있으면 모든 플랫폼에서 플러그인이 작동합니다
- 이
iOSClientIdiOS 전용 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-callbackEdge Function URL을 사용합니다. - Web에서는 Service ID와
window.location.href, Apple Return URL과 일치해야 합니다.
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: trueFacebook의 개인정보 보호 중심 Limited Login 기능을 활성화합니다 (iOS 전용)- 중요: 다음을 참고하세요. Facebook 설정 가이드 ATT 고려 사항을 포함한 상세 Limited 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만 구성하면 됩니다
- 여기 사용된 자격 증명은 원본 provider에서 가져온 것입니다, Supabase가 아닌
- Provider 자격 증명이 Supabase 설정과 일치하는지 확인하세요
React Supabase 클라이언트 및 인증 레이어
아직 Supabase 클라이언트를 설치하지 않았다면 다음 명령어를 실행하세요:
bun add @supabase/supabase-js
React 트리 전체에서 사용할 단일 클라이언트를 생성합니다:
// 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는 다음을 요구합니다: raw 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, 웹):
- iOS:
buildApplePluginConfig()반환합니다{}. Native Sign in with Apple은 bundle ID를 사용합니다. 아니요redirectUrl. - Android:
clientId은(는) Apple Service ID입니다.redirectUrl은(는) Supabase Edge Function (apple-signin-callback). 함수를 배포하고, Apple Return URL을 해당 엔드포인트로 설정한 뒤, WebView로 다시 돌아올 수 있도록 앱의 deep link intent filter를 추가합니다 ("Android Supabase Apple 가이드). - 웹:
clientId은(는) Service ID입니다.redirectUrl이며window.location.href, Apple Developer Portal의 Return URL과 일치해야 합니다 (Web Supabase Apple 가이드).
ensureSocialLoginInitialized() 를 추가합니다 apple true일 때만 provider를 사용하므로, isAppleLoginConfigured() 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 및 보호된 라우트

로그인 패널:
// 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>
);
}

Routes with 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>
);
}
앱을 다음 위치에 마운트하세요: main.tsx 평소와 동일하게 진행합니다. 프로바이더 로그인이 성공하면, onAuthStateChange 업데이트 session 및 React Router가 사용자를 /account.
선택 사항: Facebook
Facebook도 동일한 signInWithIdToken 패턴을 따릅니다. 플러그인에서 받은 액세스 토큰을 사용합니다. 자세한 내용은 Facebook 설정 및 다음의 프로바이더 블록을 참조하세요: 4단계.
React 앱에 Capacitor 추가
웹 번들을 빌드한 후, 네이티브 프로젝트를 추가하세요:
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

웹 빌드 후 매번 동기화하세요:
bun run build
bunx cap sync
기기에서 테스트할 준비가 되면 네이티브 IDE를 여세요:
bunx cap open ios
bunx cap open android
하드웨어 테스트 전 Google 및 Apple 플랫폼 설정을 완료하세요:
Deep links 및 Universal Links
호출되는 튜토리얼은 supabase.auth.signInWithOAuth WebView 내부에서 실행될 때 종종 Universal Links가 필요합니다 또는 앱 링크 그래서 Google이 리다이렉트한 뒤 OS가 앱으로 제어권을 돌려줍니다. Native Google 로그인은 ID 토큰을 플러그인이 브라우저 OAuth 왕복을 열지 않으므로 해당 리다이렉트 루프를 방지합니다. Android에서 Apple 로그인 방식은 다릅니다. 여전히 Edge Function 리다이렉트와 앱의 딥 링크 intent filter를 사용하므로 Apple 로그인 후 WebView를 재개할 수 있습니다.
다른 기능(비밀번호 재설정 이메일, 매직 링크, 마케팅 URL)을 위해 deep link를 여전히 설정할 수 있습니다. 같은 경로를 인증 > URL 설정 Supabase 및 apple-app-site-association 해당 플로우를 추가할 때 / Android intent filter에 설정합니다.
8단계: 플랫폼별 설정
iOS 설정
상세한 iOS 설정 방법은 다음 플랫폼별 가이드를 참조하세요:
- Google iOS 설정 - URL scheme, Info.plist 설정
- Apple iOS 설정 - Sign in with Apple 기능 설정
- Facebook iOS 설정 (일반 Facebook 가이드 참조) - Facebook SDK 설정
요약 - 추가 항목 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>
Xcode 프로젝트 설정을 포함한 전체 iOS 설정 방법은 연결된 가이드를 참조하세요.
Android 설정
상세한 Android 설정 방법은 플랫폼별 가이드를 확인하세요:
- Google Android 설정 - SHA-1 지문, Google Play Services 설정
- Apple Android 설정 - Android용 Service ID 설정
- Facebook Android 설정 (일반 Facebook 가이드 참조) - Facebook SDK 연동
필수 Android 설정:
1. SHA-1 지문(fingerprint) 가져오기 (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>
Google Play Services 설정 및 패키지 명 설정을 포함한 전체 Android 구성은 연결된 플랫폼 가이드를 참조하세요.
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;
}
}
주요 보안 고려 사항
- 민감한 키를 절대 노출하지 마세요 클라이언트 code에
- 환경 변수 사용 설정용
- Row Level Security 활성화 Supabase에서
- 토큰 검증 필요 시 백엔드에서 수행
- 토큰 리프레시 처리 Supabase로 자동 처리
알려진 이슈 및 문제 해결
Google Nonce 불일치 (특히 iOS)
Supabase에서 nonce 에러가 발생하면 Google이 캐시된 ID token을 반환했을 수 있습니다. 플러그인을 통해 Google에서 로그아웃한 뒤 한 번 더 시도하고, 통과하는지 확인하세요. nonceDigest 대상 SocialLogin.login 및 rawNonce 대상 signInWithIdToken. 예제 앱 이후 재시도 SocialLogin.logout.
Token audience 오류
모든 Google client ID(web, iOS, Android)를 Supabase Google provider에 등록하세요. 해당 aud ID token의 claim이 그중 하나와 일치해야 합니다.
OAuth 리다이렉트 튜토리얼 vs 이 가이드
사용하는 블로그 포스트 signInWithOAuth WebView에서는 리다이렉트가 앱 대신 Safari를 열면 모바일에서 실패할 수 있습니다. 네이티브 로그인을 권장하며, signInWithIdToken Capacitor의 경우, 또는 다음을 따르세요: Social Login 웹 설정 브라우저 리다이렉트의 경우.
플랫폼별 확인 사항
- iOS: bundle ID와 URL schemes는 Google iOS 설정에서
- Android: SHA-1 지문은 Google Android 설정에서
- 자세한 내용은 플러그인 트러블슈팅에서 통합 해결 방법을 참고하세요
토큰 불일치 및 네트워크 오류
- Google Cloud, Supabase 간의 프로바이더 자격 증명 일치시키기
SocialLogin.initialize - React에서 로딩 및 에러 상태 처리 (위의
LoginPanel내용 참조)
마치며
이제 Supabase의 강력한 백엔드와 네이티브 소셜 로그인 기능을 결합한 완벽한 인증 시스템을 갖추었습니다. 이 설정은 다음을 제공합니다:
- 안전한 네이티브 소셜 인증
- 원활한 토큰 관리
- 실시간 데이터베이스 통합
- 크로스 플랫폼 호환성
Supabase와 Capgo Social Login 플러그인의 조합은 Capacitor 앱을 위한 강력하고 확장 가능한 인증 솔루션을 제공합니다.
다음과 같은 고급 기능을 보려면 다요소 인증(MFA) 또는 커스텀 클레임(custom claims), 다음을 확인해 보세요: Supabase 문서 및 Social Login 플러그인 문서.
Capacitor Social Login 플러그인으로 Supabase 인증 설정하기 다음 단계
사용 중이라면 Capacitor Social Login 플러그인으로 Supabase 인증 설정하기 인증 및 계정 흐름을 설계할 때, 이를 다음 항목과 연결하세요: @capgo/capacitor-social-login @capgo/capacitor-social-login의 구현 세부 사항은, @capgo/capacitor-passkey @capgo/capacitor-passkey의 구현 세부 사항은 @capgo/capacitor-native-biometric @capgo/capacitor-native-biometric의 구현 세부 사항은, 2단계 인증 2단계 인증(Two-factor authentication)의 구현 세부 사항은, SSO (기업용) SSO (Enterprise)의 구현 세부 사항은.