본문 바로가기

Capacitor를 사용해 Google로 로그인하는 방법 (2026)

Capacitor 8 앱에 Google Sign-In 설정하기: 클라이언트 ID, SHA-1, iOS URL scheme, Android Credential Manager, ID token 검증 및 error 28444 해결 방법.

Capacitor 앱에서 Capgo Social Login 플러그인을 사용한 Google Sign-In 계정 선택기

Capacitor 앱에 Google Sign-In을 추가하려면 Google Cloud에서 Web, iOS 및 하나 이상의 Android OAuth 클라이언트 ID를 생성한 다음, 설치하세요 @capgo/capacitor-social-login, 호출 SocialLogin.initialize({ google: { webClientId, iOSClientId } }), 그 다음 SocialLogin.login({ provider: 'google' }). Android는 Google Credential Manager를, iOS는 Google Sign-In SDK를, 웹은 Google Identity Services를 사용합니다. 세 플랫폼 모두 서버에서 검증할 수 있는 ID 토큰을 반환합니다.

이 가이드는 2026년 10월 기준 Capacitor 8의 모든 단계를 안내하며, 특히 자주 문제가 되는 SHA-1 지문, Play App Signing 키, iOS URL 스킴 설정까지 다룹니다.

Capacitor 앱에서 Google Sign-In 작동 원리

플랫폼 플러그인이 사용하는 네이티브 API 플러그인에 전달되는 Client ID 기타 콘솔 설정
Android Credential Manager(androidx.credentials) Web client ID를 webClientId 패키지 이름과 SHA-1이 포함된 Android 클라이언트
iOS Google Sign-In SDK iOS client ID를 iOSClientId 의 Reversed client ID URL scheme Info.plist
웹 Google Identity Services Web client ID를 webClientId 승인된 JavaScript 원본

Google은 임베디드 WebView 내 OAuth를 차단합니다. disallowed_useragent 에러가 발생하여 Capacitor WebView에서 Google 로그인 페이지를 로드할 수 없습니다. 대신 이 플러그인은 네이티브 계정 선택기를 사용합니다.

1단계: Google Cloud 설정

프로젝트 및 Google Auth Platform 생성

  1. 열기 Google Cloud Console 새 프로젝트를 만들거나 기존 프로젝트를 선택하세요. 모든 클라이언트 ID는 같은 프로젝트에 있어야 합니다.
  2. 열기 Google 인증 플랫폼 (이전에 “OAuth 동의 화면”이라고 불리던 화면)
  3. 아래 브랜딩에서 사용자에게 표시할 앱 이름, 지원 이메일, 도메인, 개인정보처리방침 URL을 설정하세요.
  4. 아래 대상, 다음을 선택합니다: 외부 소비자용 앱에 적합합니다. 앱이 테스트일 경우, 아래에 나열된 계정만 테스트 사용자 로그인할 수 있습니다.
  5. 아래 데이터 액세스일 경우, 기본값을 유지하세요 openid, email 및 profile 추가가 필요하지 않는 한 기본 scopes만 사용하세요.

기본 scopes의 경우 프로덕션 배포나 인증을 통과할 필요가 없습니다. 민감한 scopes(캘린더, Drive, Gmail)는 공개 배포 전에 인증이 필요합니다.

클라이언트 ID 생성

다음으로 이동: Google Auth 플랫폼 > 클라이언트 (또는 API 및 서비스 > 사용자 인증 정보)을(를) 생성하세요:

Client 유형 입력할 내용 ID 입력 위치
웹 애플리케이션 웹 빌드용 승인된 JavaScript 출처 (예: https://app.example.com, http://localhost:5173) webClientId
iOS Bundle ID와 App Store ID, Team ID(확보 후) iOSClientId
Android (디버그) Package name과 debug SHA-1 Console 전용
Android (릴리스) Package name과 upload key SHA-1 Console 전용
Android (Play Store) Package name과 Play App Signing SHA-1 Console 전용

SHA-1 값 가져오기:

# Debug key and any signing config in build.gradle
cd android && ./gradlew signingReport

# From a signed APK you actually install
keytool -printcert -jarfile android/app/build/outputs/apk/release/app-release.apk

Play Store 빌드의 경우, 다음에서 SHA-1을 복사하세요: Play Console > 테스트 및 출시 > 앱 무결성 > 앱 서명 키 인증서. 먼저 릴리스 keystore가 필요하다면, Android 키스토어 생성기 브라우저에서 하나를 생성합니다. 저희 Google client ID 가이드 콘솔 화면에 대해 더 자세히 설명합니다.

2단계: 플러그인 설치

bun add @capgo/capacitor-social-login
bunx cap sync

SDK가 바이너리에 포함되지 않도록 사용하지 않는 provider는 비활성화하세요:

// capacitor.config.ts
import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.example.app',
  appName: 'Example',
  webDir: 'dist',
  plugins: {
    SocialLogin: {
      providers: {
        google: true,
        apple: true,
        facebook: false,
        twitter: false,
      },
    },
  },
};

export default config;

실행 bunx cap sync provider를 변경한 후.

3단계: iOS 설정

역방향 client ID URL scheme 추가

Google Cloud에서 iOS 클라이언트를 열고 다음을 복사하세요: iOS URL 스킴 (예: com.googleusercontent.apps.1234567890-abcdef). 이를 ios/App/App/Info.plist 의 닫는 태그 전에 추가하세요. </dict>:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>com.googleusercontent.apps.1234567890-abcdef</string>
    </array>
  </dict>
</array>

이미 딥링크용 CFBundleURLTypes 배열이 있다면 두 번째 키를 만들지 말고 해당 배열에 새 <dict> 항목을 추가하세요.

콜백 URL 처리는 Google SDK 에 맡기세요

다음에서 ios/App/App/AppDelegate.swift, SDK를 import하고 Capacitor 전에 URL을 해당 핸들러로 라우팅하세요:

import GoogleSignIn // add at the top of the file

// Replace the generated application(_:open:options:) method
func application(_ app: UIApplication, open url: URL,
                 options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    if GIDSignIn.sharedInstance.handle(url) {
        return true
    }
    return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
}

이 ApplicationDelegateProxy 를 호출하므로 @capacitor/app 기타 딥 링크를 계속 수신할 수 있습니다.

4단계: Android 설정

기본 스코프를 사용한 기본 sign-in은 네이티브 변경 없이 작동합니다. 추가 스코프 를 요청하거나 오프라인 모드를 사용하는 경우, 플러그인이 activity 결과를 수신해야 하므로 MainActivity 는 플러그인의 마커 인터페이스를 구현해야 합니다:

package com.example.app;

import android.content.Intent;
import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginHandle;
import ee.forgr.capacitor.social.login.GoogleProvider;
import ee.forgr.capacitor.social.login.ModifiedMainActivityForSocialLoginPlugin;
import ee.forgr.capacitor.social.login.SocialLoginPlugin;

public class MainActivity extends BridgeActivity implements ModifiedMainActivityForSocialLoginPlugin {

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode >= GoogleProvider.REQUEST_AUTHORIZE_GOOGLE_MIN
                && requestCode < GoogleProvider.REQUEST_AUTHORIZE_GOOGLE_MAX) {
            PluginHandle handle = getBridge().getPlugin("SocialLogin");
            if (handle == null) return;
            Plugin plugin = handle.getInstance();
            if (plugin instanceof SocialLoginPlugin) {
                ((SocialLoginPlugin) plugin).handleGoogleLoginIntent(requestCode, data);
            }
        }
    }

    @Override
    public void IHaveModifiedTheMainActivityForTheUseWithSocialLoginPlugin() {}
}

이것이 없으면 호출이 scopes “You CANNOT use scopes without modifying the main activity”로 거부됩니다.

에뮬레이터의 경우, 시스템 이미지에 Google Play 라벨이 있고 기기 설정에서 Google 계정에 로그인되어 있어야 합니다. Play 서비스가 없는 이미지는 NoCredentialException.

5단계: Web 설정

Web 빌드를 서빙하는 모든 origin을 Web 클라이언트의 승인된 JavaScript 원본에 추가하세요. 포트 포함 (http://localhost:5173). 호출 initialize 을 일찍 실행하세요: 플러그인이 Google의 script 태그를 주입하며, 스크립트가 준비되기 전에 실행되면 로그인에 실패합니다.

6단계: 초기화 및 로그인

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

const WEB_CLIENT_ID = '1234567890-web.apps.googleusercontent.com';
const IOS_CLIENT_ID = '1234567890-ios.apps.googleusercontent.com';

export async function initAuth() {
  await SocialLogin.initialize({
    google: {
      webClientId: WEB_CLIENT_ID, // Android + web
      iOSClientId: IOS_CLIENT_ID, // iOS
      iOSServerClientId: WEB_CLIENT_ID, // same as webClientId, required for offline mode on iOS
      mode: 'online',
    },
  });
}

export async function signInWithGoogle() {
  try {
    const { result } = await SocialLogin.login({
      provider: 'google',
      options: {},
    });

    if (result.responseType !== 'online' || !result.idToken) {
      throw new Error('Expected an ID token from Google');
    }

    const res = await fetch('https://api.example.com/auth/google', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ idToken: result.idToken }),
    });
    return res.json();
  } catch (error: any) {
    if (error?.code === 'USER_CANCELLED') return null;
    throw error;
  }
}

온라인 응답에는 다음이 포함됩니다:

필드 참고 사항
idToken OpenID Connect JWT. 이를 서버로 전송하세요
accessToken 클라이언트에서 Google API를 호출할 때 사용합니다. 다음 값일 수 있습니다 null Android에서 기본 scope만 요청하는 경우
profile email, name, givenName, familyName, imageUrl, id
responseType 'online'

Android 전용 디스플레이 옵션

이 options 객체가 허용하는 값 style: 'bottom' bottom sheet UI용, 그리고 filterByAuthorizedAccounts 및 autoSelectEnabled 재방문 사용자를 위한 설정입니다. 그대로 두세요 filterByAuthorizedAccounts Family Link 계정을 지원한다면 off로 두세요. forceRefreshToken: true 캐시된 토큰 대신 Android에 새로운 access token을 요청합니다.

추가 scope 요청

const { result } = await SocialLogin.login({
  provider: 'google',
  options: {
    scopes: ['https://www.googleapis.com/auth/calendar.readonly'],
  },
});

다음 사항을 기억하세요: MainActivity Android의 변경 사항을 적용하고, Google Cloud의 데이터 액세스 항목 아래에 scope를 추가하세요.

온라인 vs 오프라인 모드

온라인 (기본값) 오프라인
반환값 ID token, access token, 프로필 serverAuthCode 오직
백엔드 필요 토큰 검증에만 필요 네, code를 교환하려면
Google 리프레시 토큰 기기 내 Google SDK에 유지됨 서버에 저장됨
사용 시점 사용자 식별만 필요한 경우 사용자가 오프라인일 때 서버에서 Google APIs를 호출
logout, isLoggedIn, refresh 지원됨 지원되지 않음

iOS 오프라인 모드에서 필요한 것은 iOSServerClientId이며, Android에서는 MainActivity 변경이 필요합니다.

로그아웃 및 세션 확인

await SocialLogin.isLoggedIn({ provider: 'google' }); // { isLoggedIn: boolean }
await SocialLogin.logout({ provider: 'google' });

Android에서는, logout Credential Manager 상태도 함께 초기화되어, 다음 접속 시 계정 선택 창이 다시 나타납니다.

7단계: 서버에서 ID 토큰 검증하기

import { OAuth2Client } from 'google-auth-library';

const client = new OAuth2Client();

export async function verifyGoogleToken(idToken: string) {
  const ticket = await client.verifyIdToken({
    idToken,
    audience: [
      '1234567890-web.apps.googleusercontent.com',
      '1234567890-ios.apps.googleusercontent.com',
    ],
  });
  const payload = ticket.getPayload();
  if (!payload) throw new Error('Empty token payload');

  return {
    googleUserId: payload.sub,
    email: payload.email,
    emailVerified: payload.email_verified === true,
    name: payload.name,
  };
}

사용 sub 안정적인 사용자 키로 사용합니다. 이메일로 기존 계정을 연결할 때만 email_verified 이며 true두 클라이언트 ID를 모두 audience로 허용해야 모든 플랫폼의 토큰이 유효하게 유지됩니다. 그 후 자체 세션을 발급하세요. 관리형 백엔드를 사용 중이라면, Capacitor Social Login을 사용하는 Supabase 또는 Firebase Google 가이드. Neon Auth OAuth 리다이렉트를 통해 Google을 처리하며, @capgo/capacitor-neon 네이티브로 실행됩니다.

문제 해결

오류 원인 해결 방법
[28444] Developer console is not set up correctly 패키지 이름, SHA-1 또는 webClientId 불일치 Web client ID를 사용하고, 설치된 빌드의 SHA-1을 등록하며, 모든 클라이언트를 하나의 프로젝트에 유지하세요
디버그에서는 작동하지만 Play Store에서는 실패 Play에서 앱을 다시 서명함 Play 앱 서명 SHA-1로 Android 클라이언트 추가
[16] Account reauth failed 캐시된 계정 상태가 오래되었거나, 계정이 테스트 사용자가 아니거나, 사용자가 앱을 비활성화한 경우 플러그인은 자동으로 한 번 재시도합니다. 테스트 사용자, External audience, Play 서명 SHA-1을 확인하세요
NoCredentialException 기기에 Google 계정이 없거나, Play 서비스가 없는 에뮬레이터인 경우 Google Play 시스템 이미지를 사용하고 로그인하세요
USER_CANCELLED 계정을 선택한 직후 보통 여전히 SHA-1 또는 클라이언트 ID 불일치입니다 실제 취소로 판단하기 전에 콘솔 설정을 먼저 수정하세요
iOS 크래시: “다음 URL 스킴에 대한 지원이 없습니다” 에 리버스드 클라이언트 ID 누락 Info.plist 다음을 추가하세요 com.googleusercontent.apps... 스키마
Web: “주어진 오리진이 해당 클라이언트 ID에 대해 허용되지 않습니다” Web 클라이언트에 오리진 누락 포트를 포함해 정확한 오리진 추가
access_denied “앱이 Google 인증 과정을 완료하지 않았습니다” 오류 테스트 단계인데 계정이 테스트 사용자가 아님 Audience > Test users에 추가
iOS에서 Google 로그인 화면 중단 @capacitor/privacy-screen SDK 뷰를 덮음 호출하세요 PrivacyScreen.disable() 첫 번째 제출이 승인되기 login

Android에서는 Logcat을 다음으로 필터링하세요 GoogleProvider. 플러그인은 패키지 이름, 서명 SHA-1 및 마스킹된 webClientId을(를) 기록하며, 이를 콘솔과 1분 만에 비교할 수 있습니다.

배포 및 유지보수

콘솔 변경 사항은 몇 시간 후에 전파될 수 있으므로, 출시일에 클라이언트 ID를 회전하지 마세요. 네이티브 설정이 스토어 빌드에 포함된 후, 스코프, 오류 처리 또는 백엔드 URL과 같은 클라이언트 측 변경 사항은 다음을 통해 배포할 수 있습니다 Capgo Live Update 사용하기. 팀에 iOS 릴리스용 Mac이 없다면, Capgo 빌드 이 클라우드에서 iOS 바이너리를 빌드하고 서명합니다.

Capacitor 앱용 Live updates

웹 레이어 버그가 라이브 상태라면, 앱 스토어 승인을 며칠씩 기다리지 말고 Capgo를 통해 수정 사항을 배포하세요. 네이티브 변경 사항은 정상 리뷰 절차를 그대로 따르는 반면, 사용자는 백그라운드에서 업데이트를 받습니다.

블로그 최신 글

Capgo는 정말 프로페셔널한 모바일 앱을 만드는 데 필요한 최고의 인사이트를 제공합니다.