To add Sign in with Apple to a Capacitor app, install @capgo/capacitor-social-login、Appleのサインイン機能を有効にするには、App IDに SocialLogin.initialize({ apple: {} }), すると呼び出しを行います。 SocialLogin.login({ provider: 'apple' }). iOSでは、Appleのネイティブシートを開き、IDトークンを返します。 AndroidとWebでは、AppleのWeb OAuthフローを使用し、サービスIDとHTTPSリダイレクトURLが必要です。
This guide covers Apple Developer setup, the client code for iOS, Android and web, verifying the token on your backend, and the errors you are most likely to hit. It targets Capacitor 8 and Xcode 26, which App Store Connect has required for uploads since April 2026.
始める前に必要なもの
| 要件 | なぜ |
|---|---|
| Apple Developerプログラムの有料会員 | App ID、サービスID、Sign in with Appleキーは会員のみに利用可能です。 |
| Xcode 26を搭載したMac | 2026年にiOSアプリをビルドアップロードするために必要 |
| Capacitor 8プロジェクト | プラグインv8はCapacitor 8をターゲットしています |
| HTTPSドメインのコントロール | AndroidとWebのみ。Appleは拒否します。 localhost そして、プレーンHTTPの戻りURL |
| バックエンドエンドポイント | 常にトークンを検証する。Androidでは、Appleのコールバックも受け取ります。 |
署名証明書がまだない場合は、 iOS証明書生成ツール Macキーチェーンのダンスなしで作成します。
各プラットフォームでSign in with Appleのしくみ
Appleは、自社のプラットフォーム用にネイティブSDKを提供するのみなので、フローは異なります。
| プラットフォーム | メカニズム | 使用するクライアントID | ログイン用のバックエンドが必要です |
|---|---|---|---|
| iOS | ネイティブ ASAuthorizationAppleIDProvider シート |
アプリのバンドルID | いいえ |
| Android | AppleのWeb OAuthをChromeカスタムタブで実行し、Appleはサーバーにポストし、サーバーはアプリに戻ります | サービスID | はい |
| Web | AppleのJSポップアップ | サービスID | いいえ (しかし、トークンを検証する) |
アイデンティティトークンの対象者 (aud) はクライアントIDに従います。 iOS から発行されたトークンにはアプリケーションパッケージIDが含まれ、Android と Web から発行されたトークンにはサービスIDが含まれます。 バックエンドは両方を受け入れる必要があります。
ステップ 1: Apple Developer アカウントの設定
App ID に機能を有効にします。
- 機能を有効にする 開く 証明書、識別子、& プロファイル と選択.
- Open the App ID that matches your Capacitor
appIdアプリ ID を開く (例えば __CAPGO_KEEP_0__com.example.app. が存在しない場合は作成してください。 - 下記の 機能, これを Appleサインイン を選択し、保存してください。主 App ID として残してください。複数のアプリをグループ化する場合にのみ変更してください。
既存の App ID の機能を変更した場合、使用するプロビジョニング プロファイルを再生成してください。古いプロファイルには新しい特権が含まれていません。
Services ID を作成します (Android と Web のみ)
- 「 」, + をクリックし、 サービスID.
- 「例:ウェブログイン」などの説明と「例:ウェブログイン」などのIDを使用してください。
com.example.app.signin。この値はAndroidとWebで使用されます。clientId新しいサービスIDを開き、チェックボックスにチェックを入れてください。 - Appleでサインイン 、をクリックしてください。設定 Configure.
- ドメインを追加してください。
- ドメインとサブドメイン (スキームなし、例 (スキームなし、例えば)
auth.example.com). - AppleサインインをCapacitorで使用する方法 戻りURL 完全なパスで
https://例えばhttps://auth.example.com/apple/callback。ウェブログインの場合、ログインを呼び出すページのURLを、末尾のスラッシュがあるかどうかに関係なく、指定してください。
サインイン用のAppleキーを作成(アンドロイドとサーバー用トークン交換)
- 以下の手順に従ってください キーをクリック +を選択 Appleサインインを選択 設定 を選択するための主なApp IDを選択する
- キーを登録し、ファイルをダウンロードする
.p8Appleは、ファイルをダウンロードすることを一度だけ許可しています。 - 注意 キーID と チームID (開発者ポータルの右上とメンバーシップの下に表示されます)。
サーバーは、Appleのトークンエンドポイントに送信するJWTを署名するためにキーを使用します。 client_secret メールソースを登録する場合は、メールを送信します
Appleのサインイン機能をCapacitorで使用する方法
ユーザーは選択 メールを隠す, これはあなたに @privaterelay.appleid.com アドレスを与えます。 Apple は、登録したドメインとアドレスからのみメールを転送します。 サービス > Apple でメールのサインイン. この設定をスキップすると、パスワードリセットと受領メールは静かに消えます。
ステップ 2: プラグインのインストール
bun add @capgo/capacitor-social-login
bunx cap sync
プラグインは複数のプロバイダーを含みます。 Apple (および Google) のみを使用する場合は、他のプロバイダーを無効にして SDK の送信を停止してください: capacitor.config.ts Run
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 Services > Sign in with Apple for Email Communication AD_ID Google Play Console で SDK が発生する警告
Step 3: iOS の設定
- 実行
bunx cap open ios. - セレクト App 実行 Capacitor、Cloudflare、GitHub、Capgo、API、SDK、CLI、npm、bun は、Capgo の製品およびブランド名を厳密に保存します。ステップ 3: iOS の設定 実行, です。 ステップ 3: iOS の設定.
- 実行
Capacitor、Cloudflare、GitHub、Capgo、API、SDK、CLI、npm、bun は、Capgo の製品およびブランド名を厳密に保存します。 com.apple.developer.applesignin に App.entitlements. CI または Capgo ビルドを実行した場合、 ASAuthorizationError code 1000.
No Info.plist URLスキームまたは AppDelegate AppleのiOSに変更は必要ありません。
Step 4: 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.
__CAPGO_KEEP_0__
を追加します。 android/app/src/main/AndroidManifest.xml、内部の 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);
}
}
ホストでフィルタリングすると、パスワードリセット、宣伝リンクなどの他の深いリンクが機能する
コールバックエンド
Appleが 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;
items access_token そして id_token, そのリフレッシュトークンではありません、 id_token 署名と nonce サーバー上で署名を検証する前に、
署名と client_secret 独自のアプリスキーム (アプリの逆バンドルID) を使用してください。 .p8 JWTは最大6ヶ月間有効ですが、リクエストごとに短いJWTを生成することで、有効期限切れのトラブルを回避できます。
秘密管理者にファイルを保存し、アプリのバンドルに保存しないでください。
ステップ 5: Web 設定 localhostWeb ビルドでは Apple JS を使用し、サービス ID を使用します。サービス ID の戻り先 URL に、ログインをトリガーするページの URL を追加してください。Apple は
を許可していないため、Web ログインをテストするには、デプロイされたプレビュー ドメインまたは実際のホスト名を持つトンネルを使用してください。
ステップ 6: 初期化とサインイン redirectUrl AndroidおよびWebのみ。iOSでは非空 redirectUrl プラグインはそのURLに結果をPOSTします。これはネイティブフローでは望ましくありません。
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;
}
}
レスポンスは次のようになります。
| フィールド | 内容 |
|---|---|
result.idToken |
Appleから取得した署名済みJWT、サーバーが検証するもの |
result.profile.user |
Appleの安定したユーザーID(同等のclaim) sub claim) |
result.profile.email |
初回の認可のみ |
result.profile.givenName / familyName |
デフォルトの設定では、この認可__CAPGO_KEEP_0__が保持されますが、これは実際のアクセストークンではありません |
result.accessToken |
デフォルトの設定では、この値は認可 code を保持し、実際のアクセストークンではありません。 |
result.authorizationCode |
Capacitorを初期化するときに useProperTokenExchange: true |
サーバーがAppleのリフレッシュトークンを必要とする場合(例えばユーザーがアカウントを削除したときに呼び出すリバースエンドポイント)、 useProperTokenExchange: true configと apple サーバーで交換します。 authorizationCode ステップ7: バックエンドでアイデンティティトークンを検証する
Step 7: バックエンドで ID トークンを検証する
クライアントから送信されるプロファイルフィールドを信頼しない。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のSocial Login.
App Storeのルールを考慮する
- ガイドライン 4.8Google、Facebook、または他の第三者ログインを主なアカウントシステムとして提供する場合は、同等のプライバシーに焦点を当てたオプションも提供する必要があります。Sign in with Appleはそのようなオプションです。
- ガイドライン 5.1.1(v)Sign in with Appleユーザーには、Appleのエンドポイントを呼び出すことなくアカウントを削除できるようにする必要があります。詳細は、
https://appleid.apple.com/auth/revokeアカウント削除ガイド ボタン設計. - : Appleの承認済みボタンスタイルを使用し、他のログインボタンと同じ程度の目立つボタンを使用する必要があります。トラブルシューティング
症状
| 原因 | 解決策 | Fix |
|---|---|---|
ASAuthorizationError code 1000 iOS |
iOSビルドターゲットに機能または特権が欠けている | XcodeにSign in with Appleを追加し、コミット App.entitlementsプロファイルを再生成 |
| Code 1001 iOS | ユーザーがキャンセル | プラグインが拒否 USER_CANCELLED, これをエラーとして扱わない |
invalid_client AndroidまたはWeb上で |
間違った clientId (bundle IDが代わりにServices IDが使用されている)または悪い client_secret |
Services IDを使用し、Team ID、Key ID、およびキー内容を確認 |
invalid_request: Invalid redirect_uri |
正確に登録されていないReturn URL | URLを完全に追加 https:// URLの末尾のスラッシュを含めてサービスIDのURLを追加 |
| Androidログイン後にアプリが戻らない | Androidログイン後のサーバーからリダイレクトされるURLのスキームまたはホストが意図したものと異なる | URLを com.example.app://apple-login 文字列を文字列単位で比較 |
apple.android.redirectUrl is null or empty |
Android redirectUrl Android |
名前とメールアドレスは |
最初の認証ではない null |
データベースから読み取り、再度アプリをデバイスの設定で削除してテストする | 比較 |
バックエンドからトークンが拒否されました: aud 一致しません |
サーバーは1つのアウディエンスのみを受け付ける | 両方のバンドルIDとサービスIDを受け付ける |
リリース後の修正
ほとんどの認証の問題はTypeScriptにあります:プラットフォームごとに間違ったクライアントID、nonceが欠けている、エラー処理が悪い。ユーザーに同じ日に配信できるのは Capgoのライブアップデート アプリレビューを待つ必要なく MainActivity 特権の変更、Androidマニフェスト、
ネイティブな変更は新しいストアビルドが必要です
- 次のステップはどこですか GoogleもAppleの横に追加してください:CapacitorでGoogleにサインインする方法.
- アプリの機能を選択してください How to add authentication to a Capacitor app.
- プラグインのリファレンスとプラットフォームのページ ソーシャルログインのドキュメント そして Apple iOSの設定ページ.