애플로 로그인 기능을 Capacitor 앱에 추가하려면 설치 @capgo/capacitor-social-loginSign in with Apple 기능을 앱 ID에 활성화하고 Xcode에서 활성화한 다음 SocialLogin.initialize({ apple: {} })그런 다음 SocialLogin.login({ provider: 'apple' }). iOS에서 이 앱은 native Apple sheet를 열고 identity token을 반환합니다. Android와 웹은 Apple의 웹 OAuth 흐름을 사용하며, Services ID와 HTTPS redirect URL이 필요합니다.
이 안내서에서는 Apple Developer 설정, iOS, Android 및 웹용 클라이언트 code 설정, 백엔드에서 토큰을 확인하는 방법, 그리고 가장 자주 발생하는 오류에 대해 설명합니다. 이 안내서는 Capacitor 8과 Xcode 26을 대상으로 하며, App Store Connect는 2026년 4월부터 업로드를 위해 이 버전이 필요합니다.
시작하기 전에 필요한 것
| 요구 사항 | 왜 |
|---|---|
| Apple Developer 프로그램의 유료 회원 가입 | App IDs, Services IDs 및 Sign in with Apple 키는 회원만 사용할 수 있습니다. |
| Xcode 26이 설치된 Mac | 2026년 iOS 앱을 빌드하고 업로드하려면 필요합니다. |
| Capacitor 8 프로젝트 | 플러그인 v8은 Capacitor 8 버전을 대상으로 합니다. |
| 관리할 HTTPS 도메인 | Android와 웹만 지원. 애플은 localhost plain HTTP 반환 URL |
| 백엔드 엔드포인트 | 항상 토큰을 확인합니다. Android에서는 Apple의 콜백도 받습니다. |
인증서를 아직 만들지 않은 경우, iOS 인증서 생성기 Mac 키 체인 댄스 없이 인증서를 생성합니다.
Sign in with Apple의 플랫폼별 작동 방식
애플은 native SDK를만들어 제공하기 때문에 플랫폼별로 흐름이 다릅니다.
| 플랫폼 | 기능 | 사용하는 Client ID | 로그인에 필요한 백엔드 |
|---|---|---|---|
| iOS | 자연어 ASAuthorizationAppleIDProvider sheet |
시트 | 앱의 번들 ID |
| 아니요 | 안드로이드 | Services ID | 서비스 ID |
| 네 | 웹 | 서비스 ID | 아니오 (하지만 토큰을 확인하세요) |
사용자 ID의 대상 (aud)은 클라이언트 ID를 따릅니다. iOS에서 발급된 토큰은 앱의 번들 ID를 포함하고, Android 및 웹에서 발급된 토큰은 서비스 ID를 포함합니다. 백엔드에서는 두 가지 모두를 수락해야 합니다.
1단계: Apple Developer 계정 구성
1단계: Apple Developer 계정 구성
- Open 열기 인증서, 식별자 및 프로필 식별자 선택.
- 앱 ID 열기 (예를 들어 Capacitor
appId(예를 들어com.example.app. 만든다. - 아래 능력, 체크 애플 로그인 및 저장. 기본 App ID로 남겨두거나 여러 앱을 묶을 때만 변경.
기존 App ID의 기능을 변경하면 사용하는 프로비전 프로파일을 다시 생성해야 합니다. 새로운 권한이 포함되지 않은旧 프로파일은 사용할 수 없습니다.
서비스 ID 생성 (안드로이드 및 웹 전용)
- 에서 식별자, 클릭 + 및 선택 서비스 ID.
- ‘예시 웹 로그인’과 같은 설명과 ‘예시 웹 로그인’과 같은 식별자를 사용하세요. 이 값은 안드로이드와 웹에서 사용됩니다.
com.example.app.signin새로운 서비스 ID를 열고 ‘예시 웹 로그인’과 같은 설명과 ‘예시 웹 로그인’과 같은 식별자를 사용하세요.clientId애플 로그인 - , 설정, 클릭 Configure.
- (예를 들어
- 등록한 도메인 목록에 추가하십시오 도메인과 서브 도메인 (예를 들어 scheme이 없는 경우)
auth.example.com). - Apple 로그인에 사용할 리턴 URL을 추가하십시오. 리턴 URL 전체 경로와 함께, 예를 들어
https://. 웹 로그인에 사용할 경우 로그인 호출하는 페이지 URL을, 슬래시(/)가 있는 경우와 없는 경우 모두 입력하십시오.https://auth.example.com/apple/callbackApple 로그인 키를 생성하십시오 (Android 및 서버 토큰 교환)
Apple 로그인 키를 생성하려면
- , 키, +클릭 Apple 로그인, 설정 기본 App ID를 선택하기 위해
- 등록 키와 다운로드할
.p8파일을 등록하세요. Apple은 파일을 한번만 다운로드할 수 있으니 주의하세요. - 주의 사항 Key ID 그리고 팀 ID (개발자 포털의 상단 오른쪽과 회원 가입 아래에 표시됩니다.)
서버는 키를 사용하여 Apple의 토큰 엔드포인트로 보낸 JWT를 서명합니다. client_secret 이메일 출처를 등록하세요. 이메일을 보내는 경우
이메일 소스 등록
사용자는 선택할 수 있습니다. 이메일 숨기기, 이 주소가 제공됩니다. Apple은 등록한 도메인과 주소만 전달합니다. @privaterelay.appleid.com 서비스 > Apple과 함께 로그인하기 . 이메일 전송을 위해 이 설정을 건너뛰면 비밀번호 재설정 및 수표 이메일이 조용히 사라집니다.Step 2: Capacitor 플러그인 설치
Capacitor 플러그인은 여러 제공자를 포함합니다. Apple (및 Google)만 사용하는 경우 다른 제공자를 끄세요.
bun add @capgo/capacitor-social-login
bunx cap sync
소프트웨어 SDK가 배포되지 않도록 하세요: capacitor.config.ts Capacitor 플러그인을 다시 설치하세요.
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Example',
webDir: 'dist',
plugins: {
SocialLogin: {
providers: {
apple: true,
google: false,
facebook: false,
twitter: false,
},
},
},
};
export default config;
Run bunx cap sync Capacitor 플러그인을 다시 설치하세요. AD_ID Google Play Console에서 SDK이 발생시키는 허용 경고입니다.
Step 3: iOS 설정
- 실행
bunx cap open ios. - 선택하세요 App Capacitor을 사용하여 Apple과 함께 로그인하는 방법 Capacitor을 사용하여 Apple과 함께 로그인하는 방법, click + Capability, 그리고 추가 Capacitor을 사용하여 Apple과 함께 로그인하는 방법.
- Capacitor을 사용하여 Apple과 함께 로그인하는 방법
Capacitor을 사용하여 Apple과 함께 로그인하는 방법 com.apple.developer.applesignin into App.entitlements. 만약 CI에서 빌드하거나 Capgo 빌드를 커밋하지 않으면, 클라우드 빌드는 실패하는 앱을 생성합니다. ASAuthorizationError code 1000.
No Info.plist iOS에서 Apple에 대한 AppDelegate Step 4: Android 설정
Apple은 Android
Apple has no Android SDK. The plugin opens Apple’s authorize page in a Custom Tab with your Services ID. Apple then sends the result as a form POST to your HTTPS return URL. Your server exchanges the code, then redirects to a deep link that reopens the app with the tokens.
를 교환하고,
를 재개합니다. android/app/src/main/AndroidManifest.xml애플 로그인에 사용하는 Capacitor를 사용하는 방법 MainActivity <activity> 요소:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.example.app" android:host="apple-login" />
</intent-filter>
플러그인으로 인텐트를 전달하세요
편집 MainActivity.java 이러한 플러그인으로 도달하는 깊은 링크가 되도록 편집하세요:
package com.example.app;
import android.content.Intent;
import android.net.Uri;
import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginHandle;
import ee.forgr.capacitor.social.login.SocialLoginPlugin;
public class MainActivity extends BridgeActivity {
@Override
protected void onNewIntent(Intent intent) {
Uri data = intent.getData();
if (Intent.ACTION_VIEW.equals(intent.getAction()) && data != null
&& "apple-login".equals(data.getHost())) {
PluginHandle handle = getBridge().getPlugin("SocialLogin");
if (handle != null) {
Plugin plugin = handle.getInstance();
if (plugin instanceof SocialLoginPlugin) {
((SocialLoginPlugin) plugin).handleAppleLoginIntent(intent);
return;
}
}
}
super.onNewIntent(intent);
}
}
호스트에서 필터링하면 다른 깊은 링크(비밀번호 초기화, 마케팅 링크)가 작동합니다.
콜백 엔드포인트
애플이 code, id_token, state첫 로그인 시 user JSON 문자열을 반환 URL로 전송합니다. 엔드포인트는 code를 교환하고 앱으로 리다이렉트해야 하며, 플러그인이 파싱하는 계약입니다. Hono 예제를 사용하여 최소한의 success=true 플러그인이 토큰을 깊은 링크 쿼리 문자열에서 읽습니다. 따라서 앱이 동일한 커스텀 스키마를 등록하는 경우 로그에 표시하거나 다른 앱이 읽을 수 있습니다. 단기 토큰만 전송하세요. jose:
import { Hono } from 'hono';
import { SignJWT, importPKCS8, createRemoteJWKSet, jwtVerify } from 'jose';
import { saveAppleRefreshToken } from './db'; // your own persistence layer
const app = new Hono();
const TEAM_ID = process.env.APPLE_TEAM_ID!;
const KEY_ID = process.env.APPLE_KEY_ID!;
const SERVICE_ID = process.env.APPLE_SERVICE_ID!; // com.example.app.signin
const PRIVATE_KEY = process.env.APPLE_PRIVATE_KEY!; // contents of the .p8 file
const APP_REDIRECT = 'com.example.app://apple-login';
const APPLE_JWKS = createRemoteJWKSet(new URL('https://appleid.apple.com/auth/keys'));
async function appleClientSecret() {
const key = await importPKCS8(PRIVATE_KEY, 'ES256');
return new SignJWT({})
.setProtectedHeader({ alg: 'ES256', kid: KEY_ID })
.setIssuer(TEAM_ID)
.setSubject(SERVICE_ID)
.setAudience('https://appleid.apple.com')
.setIssuedAt()
.setExpirationTime('5m')
.sign(key);
}
app.post('/apple/callback', async (c) => {
const form = await c.req.parseBody();
const code = String(form.code ?? '');
if (!code) {
return c.redirect(`${APP_REDIRECT}?success=false`);
}
// `form.user` is only present on the first authorization. Store it now.
const res = await fetch('https://appleid.apple.com/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: SERVICE_ID,
client_secret: await appleClientSecret(),
code,
grant_type: 'authorization_code',
redirect_uri: 'https://auth.example.com/apple/callback',
}),
});
if (!res.ok) {
return c.redirect(`${APP_REDIRECT}?success=false`);
}
const tokens = await res.json();
// Verify the identity token, then keep the long-lived refresh token on the server.
// Only the short-lived tokens go back to the app in the deep link.
const { payload } = await jwtVerify(tokens.id_token, APPLE_JWKS, {
issuer: 'https://appleid.apple.com',
audience: SERVICE_ID,
});
if (tokens.refresh_token) {
await saveAppleRefreshToken(String(payload.sub), tokens.refresh_token);
}
const params = new URLSearchParams({
success: 'true',
access_token: tokens.access_token,
id_token: tokens.id_token,
});
return c.redirect(`${APP_REDIRECT}?${params}`);
});
export default app;
The plugin reads the tokens from the deep link query string, so anything you put there can end up in logs or be read by another app that registers the same custom scheme. Send only the short-lived access_token 그리고 id_token, 새로고침 토큰이 아닌 id_token 서명과 nonce 서버에서 확인하기 전에, 앱의 역방향 번들 ID가 달린 고유한 스키마를 사용하세요.
The client_secret JWT는 6개월까지 유효할 수 있지만, 요청당 짧은 JWT를 생성하여 만료 오류를 피하세요. 파일을 비밀 관리자에 저장하세요, 앱 번들에 저장하지 마세요. .p8 5단계: 웹 설정
웹 빌드는 Apple JS와 Services ID를 사용합니다. 로그인 트리거 URL을 Services ID의 반환 URL에 정확히 추가하세요. Apple은
,이러한 URL을 허용하지 않습니다. 따라서 웹 로그인 테스트를 미리 배포된 도메인 또는 실제 호스트 이름이 있는 터널을 통해 진행하세요. localhost6단계: 초기화 및 로그인
앱 시작 시 한 번 초기화하고,
context redirectUrl Android와 웹에서만. iOS에서는 비어 있지 않은 redirectUrl 플러그인으로 POST를 보내는 URL을 지정하면, native flow에서 원치 않는 결과가 발생합니다.
import { Capacitor } from '@capacitor/core';
import { SocialLogin } from '@capgo/capacitor-social-login';
const platform = Capacitor.getPlatform();
export async function initAuth() {
await SocialLogin.initialize({
apple: {
clientId: platform === 'ios' ? 'com.example.app' : 'com.example.app.signin',
redirectUrl:
platform === 'android'
? 'https://auth.example.com/apple/callback'
: platform === 'web'
? 'https://app.example.com/login'
: '',
},
});
}
버튼에서 로그인 트리거를 호출하세요. 서버에서 재생된 토큰을 거부하기 위해 랜덤한 nonce를 사용하세요:
import { SocialLogin } from '@capgo/capacitor-social-login';
export async function signInWithApple() {
const nonce = crypto.randomUUID();
try {
const { result } = await SocialLogin.login({
provider: 'apple',
options: {
scopes: ['email', 'name'],
nonce,
},
});
if (!result.idToken) {
throw new Error('Apple did not return an identity token');
}
// Send to your backend. Name and email are only present on the first login.
const response = await fetch('https://api.example.com/auth/apple', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
idToken: result.idToken,
nonce,
givenName: result.profile.givenName,
familyName: result.profile.familyName,
}),
});
return response.json();
} catch (error: any) {
if (error?.code === 'USER_CANCELLED') {
return null; // user closed the sheet, not a failure
}
throw error;
}
}
응답은 다음과 같습니다:
| Field | Content |
|---|---|
result.idToken |
애플에서 발급한 서명된 JWT, 서버에서 확인하는 것 |
result.profile.user |
애플의 안정적인 사용자 ID (claim과 동일) sub claim) |
result.profile.email |
첫 번째 인증만 |
result.profile.givenName / familyName |
기본 설정으로는 인증 __CAPGO_KEEP_0__을 보유하고, 실제 액세스 토큰이 아닌 |
result.accessToken |
Capacitor의 기본 설정으로는 인증 code을 저장하고, 실제 접근 토큰이 아닌 것입니다. |
result.authorizationCode |
초기화 시에 useProperTokenExchange: true |
서버에서 Apple 리프레시 토큰이 필요하다면 (예를 들어 사용자가 계정을 삭제할 때 revoke 엔드포인트를 호출할 때), useProperTokenExchange: true 에서 apple 서버에서 교환합니다. authorizationCode 7단계: 백엔드에서 사용자 식별 토큰을 확인하세요
7단계: 백엔드에서 사용자 식별 토큰을 확인하세요
클라이언트가 보내는 프로필 필드에 절대 신뢰하지 마세요. Apple의 공개 키와 JWT 서명과 클레임을 확인하세요:
import { createRemoteJWKSet, jwtVerify } from 'jose';
const appleKeys = createRemoteJWKSet(new URL('https://appleid.apple.com/auth/keys'));
export async function verifyAppleToken(idToken: string, expectedNonce: string) {
const { payload } = await jwtVerify(idToken, appleKeys, {
issuer: 'https://appleid.apple.com',
audience: ['com.example.app', 'com.example.app.signin'], // bundle ID + Services ID
});
if (payload.nonce !== expectedNonce) {
throw new Error('Nonce mismatch');
}
return {
appleUserId: payload.sub as string,
email: payload.email as string | undefined,
emailVerified: payload.email_verified === true || payload.email_verified === 'true',
};
}
사용자 레코드를 sub에 저장하세요, 이메일이 아닌. 이메일은 중계 주소일 수 있고 사용자가 이를 공유하지 않도록 할 수 있습니다. 확인 후, 자신의 세션 토큰을 발급하세요. Supabase 또는 Firebase를 사용하는 경우, 토큰을 확인해 주세요. Supabase와 Capacitor 소셜 로그인.
4.8 지침
- App Store 규칙을 참고하세요: Google, Facebook 또는 다른 세 번째 로그인 옵션을 제공한다면, Apple 로그인과 같은 개인 정보 보호에 중점을 둔 동등한 옵션도 제공해야 합니다.
- 5.1.1(v) 지침: 사용자가 계정을 만들 수 있는 앱은 사용자가 앱에서 계정을 삭제할 수 있어야 합니다. Apple 로그인 사용자에게는 Apple의 삭제 엔드포인트를 호출해야 합니다. 자세한 내용은 계정 삭제 가이드를 참조하세요.
https://appleid.apple.com/auth/revoke버튼 디자인 : Apple의 승인된 버튼 스타일을 사용하고 다른 로그인 버튼과 같은 중요성을 지닌 버튼을 사용해야 합니다.. - 문제 해결증상
가능한 원인
| 해결 | __CAPGO_KEEP_0__ 1000 on iOS | Fix |
|---|---|---|
ASAuthorizationError code 1000 iOS에서 |
iOS 빌드 대상에서 capability 또는 권한이 누락됨 | Xcode에서 Sign in with Apple 추가, 커밋 App.entitlements다시 프로파일 생성 |
| Code 1001 iOS 버전 | 사용자가 취소함 | 플러그인이 거부함 USER_CANCELLED, 오류로 간주하지 않음 |
invalid_client iOS, Android 또는 웹에서 |
잘못됨 clientId (Services ID 대신 Bundle ID 사용 또는 키 내용이 잘못됨) client_secret |
Services ID 사용, 팀 ID, 키 ID 및 키 내용 확인 |
invalid_request: Invalid redirect_uri |
등록된 URL과 정확히 일치하지 않음 | URL을 완전히 추가하세요 https:// URL에 Services ID를 완전히 추가하세요. 끝에 슬래시(/)가 일치해야 합니다. |
| Android 로그인 후 앱이 다시 돌아오지 않습니다. | Android 로그인 후 앱이 다시 돌아오지 않습니다. | Compare com.example.app://apple-login 비교 |
apple.android.redirectUrl is null or empty |
문자 하나하나 비교 redirectUrl 아니요 |
안드로이드 |
안드로이드에서 HTTPS callback URL을 초기화할 때 전달하세요. null |
이름과 이메일은 | 첫 번째 인증이 아닙니다. |
백엔드에서 토큰이 거부되었습니다: aud 일치하지 않음 |
서버는 하나의 청중만 허용합니다. | 배달 ID와 서비스 ID를 모두 수락하십시오. |
릴리스 후 배송 수정
대부분의 인증 문제는 TypeScript에서 발생합니다: 플랫폼당 잘못된 클라이언트 ID, 비어있는 비회한, 나쁜 오류 처리. 사용자에게 이러한 문제를 다음날에 배달할 수 있습니다. Capgo 라이브 업데이트 앱 리뷰를 기다리지 않고도. MainActivity 권한 변경, 안드로이드 매니페스트 또는
변경 사항은 네이티브이므로 새로운 스토어 빌드를 필요로합니다.
- 다음으로 이동하세요 애플 다음에 구글을 추가하세요: Capacitor를 사용하여 구글과 로그인하세요.
- 어떤 방법을 선택해야 할까요? 애플 로그인 기능을 Capacitor 앱에 추가하는 방법.
- 플러그인 참조 및 플랫폼 페이지 사회 로그인 문서 그리고 Apple iOS 설정 페이지.