Capacitor アプリに Sign in with Apple を追加するには、インストールします @capgo/capacitor-social-login、App ID と Xcode で Sign in with Apple の機能を有効にし、呼び出します SocialLogin.initialize({ apple: {} })、その後呼び出す SocialLogin.login({ provider: 'apple' })。iOS ではネイティブの Apple シートが開き、identity token が返されます。Android と Web は Apple の Web OAuth フローを使用し、Services ID と HTTPS リダイレクト URL が必要です。
このガイドでは、Apple Developer のセットアップ、iOS/Android/Web 向けのクライアント code、バックエンドでのトークン検証、および遭遇しやすいエラーについて説明します。対象は Capacitor 8 と Xcode 26 で、2026年4月以降 App Store Connect のアップロードに必須とされています。
開始前に必要なもの
| 要件 | 理由 |
|---|---|
| 有料の Apple Developer Program 会員資格 | App ID、Services 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を提供していないため、フローが異なります:
| プラットフォーム | 仕組み | 使用するClient ID | ログインに必要なバックエンド |
|---|---|---|---|
| iOSと | ネイティブ ASAuthorizationAppleIDProvider シート |
アプリのBundle ID | いいえ |
| Androidでは | Chrome Custom TabでAppleのWeb OAuthを実行し、AppleがサーバーにPOST、サーバーがアプリへリダイレクトします | Services ID | はい |
| Web | Apple JS ポップアップ | Services ID | いいえ(ただしトークンは検証してください) |
Audience(aud)は client ID に応じて決まります。iOS のトークンには bundle ID が、Android と Web のトークンには Services ID が入ります。バックエンドでは両方を受け入れる必要があります。
ステップ 1: Apple Developer アカウントの設定
App ID の capability を有効にする
- 開く Certificates, Identifiers & Profilesで作成します。 を選択し、 識別子.
- Capacitor に一致する App ID を開く
appId(例:com.example.app)。存在しない場合は作成してください。 - 未満 機能を開き、 Appleでサインイン して保存します。複数のアプリをグループ化しない限り、プライマリ App ID のままにしてください。
既存の App ID の capabilities を変更した場合、それを使用している provisioning profiles を再生成してください。古い profile には新しい entitlement が含まれません。
Services ID を作成(Android と Web のみ)
- では 識別子、クリックして + を選択します サービスID.
- 「Example Web Login」のような説明と、
com.example.app.signinのような識別子を使用します。この値はclientIdがAndroidとWebでになります。 - 新しいサービスIDを開き、 Appleでサインイン、クリックして 設定.
- App ID をプライマリ App ID として選択します。
- ドメインを以下に追加します: ドメインとサブドメイン (スキームなし、例:
auth.example.com). - 以下にリターンURLを追加: リターンURL 完全な
https://パス、例:https://auth.example.com/apple/callback。Webログインの場合は、末尾スラッシュの有無にかかわらず、ログインを呼び出すページのURLを追加してください。
Sign in with Apple キーを作成(Android とサーバーでのトークン交換用)
- 以下に移動します: キー、クリックして +を開き、 Appleでサインイン、クリックして 設定 プライマリ App ID を選択します。
- キーを登録し、ファイルをダウンロードしてください
.p8Apple では一度しかダウンロードできません。 - 以下を控えてください: Key ID と、あなたの チームID (デベロッパーポータルの右上と Membership 内に表示されます)。
サーバーはこのキーを使って、 client_secret Apple のトークンエンドポイントに送信する JWT に署名します。
メールを送信する場合は、メールソースを登録してください
ユーザーが選択可能 Hide My Email、これにより @privaterelay.appleid.com アドレスが提供されます。Appleは、登録したドメインとアドレスからのメールのみ転送します サービス > メール通信用の Sign in with Apple。これをスキップすると、パスワードリセットや領収書のメールが静かに失われます。
ステップ 2: プラグインのインストール
bun add @capgo/capacitor-social-login
bunx cap sync
このプラグインは複数のプロバイダーをバンドルしています。Apple(および場合によってはGoogle)のみを使用する場合は、他のプロバイダーを無効にしてください capacitor.config.ts そのため、これらのSDKはバンドルに含まれません:
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;
実行 bunx cap sync このブロックを変更した後に再度実行してください。無効化されたプロバイダーは実行時に利用できないため、呼び出しているプロバイダーを無効にしないでください。Facebookを削除すると、 AD_ID Google Play ConsoleでそのSDKが発生させる権限警告も表示されなくなります。
ステップ3: iOSセットアップ
- 実行
bunx cap open ios. - 次の アプリ ターゲットを選択し、 Signing & Capabilities、クリックして + 機能、追加します Appleでサインイン.
- バンドル識別子が設定した App ID と一致することを確認してください。
Xcode は com.apple.developer.applesignin 次のように分類しています App.entitlementsを書き出します。CI や Capgo のビルドでビルドする場合は、そのエンタイトルメントファイルをコミットしてください。そうしないとクラウドビルドで生成されたアプリが ASAuthorizationError code 1000 で失敗します。
いいえ Info.plist URL スキームまたは AppDelegate の変更が iOS の Apple に対して必要です。
ステップ4: Android のセットアップ
Apple には Android 用の SDK はありません。プラグインは Services ID を使って Apple の認可ページを Custom Tab で開きます。その後、Apple は結果をフォーム POST として HTTPS のリターン URL に送信します。サーバーは code を交換してから、トークン付きでアプリを再度開くディープリンクにリダイレクトします。
ディープリンクを追加
では 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>
Intent をプラグインに転送
編集 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 が POST します code, id_token, state、初回ログイン時に user JSON文字列をリターンURLに渡します。エンドポイントはcodeを交換し、アプリにリダイレクトする必要があります success=true トークンも含まれます。これがプラグインが解析する契約です。最小限のHonoの例として 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;
プラグインはディープリンクのクエリ文字列からトークンを読み取ります。そのため、そこに含めた情報はログに残ったり、同じカスタムスキームを登録した別のアプリに読み取られたりする可能性があります。短期間の access_token と id_tokenのみを送信し、リフレッシュトークンは送らず、 id_token 署名を検証し nonce サーバー側で信頼する前に確認し、アプリ固有のスキーム(リバースドメイン形式のバンドルID)を使用してください。
この client_secret JWTは最大6ヶ月間有効ですが、リクエストごとに短期間のものを作成することで、期限切れによる障害を回避できます。 .p8 ファイルをシークレットマネージャーに保管し、アプリバンドル内には決して含めないでください。
ステップ5: Webセットアップ
Webビルドでは、Services IDとApple JSを使用します。ログインをトリガーする正確なページURLを、Services IDのリターンURLに追加してください。Appleは localhostを受け入れないため、デプロイ済みのプレビュードメインまたは実ホスト名を持つトンネル経由で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( sub claim) |
result.profile.email |
許可された場合のメールアドレスまたはプライベートリレーアドレス |
result.profile.givenName / familyName |
初回の認可時のみ |
result.accessToken |
デフォルト設定では、これは実際のアクセストークンではなく、認可codeを保持します |
result.authorizationCode |
で初期化すると存在します useProperTokenExchange: true |
サーバーがAppleのリフレッシュトークンを必要とする場合(たとえば、ユーザーがアカウントを削除したときにrevokeエンドポイントを呼び出す場合)は、設定してください useProperTokenExchange: true の apple config を設定して交換します authorizationCode サーバー上で。
ステップ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.8:Google、Facebook、またはその他のサードパーティログインをメインのアカウントシステムとして提供する場合、同等のプライバシー重視のオプションも提供する必要があります。Sign in with Appleはこれに該当します。
- ガイドライン 5.1.1(v):ユーザーがアカウントを作成できるアプリは、アプリ内でアカウントを削除できるようにする必要があります。Sign in with Appleユーザーの場合、削除時にAppleの
https://appleid.apple.com/auth/revokeエンドポイントを呼び出してください。当社の アカウント削除ガイド. - ボタンのデザイン:Appleが承認したボタンスタイルを使用し、他のログインボタンと同程度以上に目立たせる必要があります。
トラブルシューティング
| 症状 | 考えられる原因 | 解決策 |
|---|---|---|
ASAuthorizationError iOSでのcode 1000 |
ビルドターゲットにCapabilityまたはentitlementが不足しています | XcodeでSign in with Appleを追加してコミットし、 App.entitlementsプロファイルを再生成してください |
| iOSでのCode 1001 | ユーザーがキャンセルしました | プラグインは次のエラーでrejectします: USER_CANCELLEDエラーとして扱わないでください |
invalid_client AndroidまたはWeb環境で |
誤り clientId (Services IDではなくbundle IDが使用されている) または不正な client_secret |
Services IDを使用し、Team ID、Key ID、およびキーの内容を確認してください |
invalid_request: Invalid redirect_uri |
Return URLが完全一致で登録されていません | 完全な https:// URLをServices IDに追加し、末尾のスラッシュまで一致させてください |
| Androidでログイン後にアプリに戻らない | Intent filterのschemeまたはhostがサーバー側のredirectと異なります | 比較 com.example.app://apple-login 1文字ずつ |
apple.android.redirectUrl is null or empty |
いいえ redirectUrl Androidの場合 |
Androidの初期化時にHTTPSコールバックURLを渡してください |
名前とメールアドレスは null |
初回の認可ではありません | データベースから読み取ってください。再テストするには、デバイスの設定でアプリの認可を取り消してください |
バックエンドでトークンが拒否されました: aud 不一致 |
サーバーはaudienceを1つしか受け付けません | bundle IDとServices IDの両方を受け入れてください |
リリース後の修正を配信
Most auth bugs live in TypeScript: a wrong client ID per platform, a missing nonce, bad error handling. Those can be shipped to users the same day with Capgo Live Updates App Reviewを待つ必要はありません。entitlementsやAndroid manifest、または MainActivity ネイティブ側の変更なので、ストア用の新しいビルドが必要です。
次のステップ
- Appleに加えてGoogleも追加: Capacitor を使った Google サインイン方法.
- アプリに最適な認証方法の組み合わせを選択: Capacitorアプリに認証を追加する方法.
- プラグインの参考情報とプラットフォームページ: Social Login ドキュメント および Apple iOS セットアップページ.