Per aggiungere l'accesso con Apple a un Capacitor app, installa @capgo/capacitor-social-loginAbilita la capacità di accesso con Apple sul tuo ID App e in Xcode, chiama SocialLogin.initialize({ apple: {} })Chiamare SocialLogin.login({ provider: 'apple' })On iOS questo apre la schermata nativa di Apple e restituisce un token di identità. Android e web utilizzano il flusso OAuth web di Apple, che richiede un ID Servizi e un URL di reindirizzamento HTTPS.
Questa guida copre la configurazione dello sviluppatore Apple, il client code per iOS, Android e web, la verifica del token sul tuo backend e gli errori che sei più probabile di incontrare. Si concentra su Capacitor 8 e Xcode 26, che App Store Connect richiede per le pubblicazioni dal 2026.
Cosa serve prima di iniziare
| Requisito | Perché |
|---|---|
| Iscrizione al programma di sviluppo Apple a pagamento | App IDs, Services IDs e chiavi Sign in with Apple sono disponibili solo per i membri |
| Un Mac con Xcode 26 | È necessario per creare e caricare app iOS nel 2026 |
| Capacitor 8 progetto | Plugin v8 che mira a Capacitor 8 |
| Un dominio HTTPS che controlli | Solo per Android e web. Apple rifiuta localhost e ritorna URL HTTP semplice |
| Punto finale backend | Sempre per verificare i token. Su Android riceve anche il callback di Apple |
Se non hai ancora i certificati di firma, il generatore di certificato iOS crea senza una danza di keychain Mac.
Come funziona l'accesso con Apple su ogni piattaforma
Apple invia solo un nativo SDK per le proprie piattaforme, quindi il flusso differisce:
| Piattaforma | Meccanismo | ID client utilizzato | Richiesta di backend per l'accesso |
|---|---|---|---|
| IOS | Nativo ASAuthorizationAppleIDProvider sheet |
Finestra di dialogo | No |
| No | Apple OAuth web in una finestra personalizzata di Chrome, Apple invia i dati al tuo server, il server reindirizza nuovamente all'app. | ID dei Servizi | Yes |
| Sì | Apple popup JS | Servizi ID | No (ma verificare il token) |
Il pubblico ('aud) del token di identità segue l'ID del client. I token da iOS portano il tuo ID bundle, i token da Android e web portano il Servizi ID. Il tuo backend deve accettare entrambi.
Passo 1: Configura l'account sviluppatore Apple
Abilita la capacità sul tuo ID App
- Apre Certificati, identificatori e profili e seleziona Identificatori.
- Apre l'ID App che corrisponde al tuo Capacitor
appId(ad esempiocom.example.app). Create it if it does not exist. - Sotto Funzionalità, segna Accedi con Apple e salvalo. Lascialo come ID App principale a meno che non si tratta di un gruppo di app.
Se modifichi le funzionalità di un ID App esistente, rigenera i profili di provisioning che lo utilizzano. I vecchi profili non includono l'entità nuova.
Crea un ID Servizi (solo per Android e web)
- In Identificatori, clicca + e scegli ID dei Servizi.
- Usa una descrizione come “Esempio di accesso web” e un identificatore come
com.example.app.signin. Questo valore diventa ilclientIdsu Android e web. - Apre il nuovo ID dei Servizi, seleziona Accedi con Apple, e clicca su Configura.
- Scegli il tuo ID App come ID App principale.
- Aggiungi il tuo dominio sotto Domini e sottodomini (senza schema, ad esempio
auth.example.com). - Aggiungi gli URL di ritorno sotto URL di ritorno con la strada completa, ad esempio
https://percorso, ad esempiohttps://auth.example.com/apple/callbackCreare una chiave di accesso Sign in with Apple (token di scambio Android e server)
Crea una chiave Sign in with Apple (scambio token Android e server)
- Chiavi , clicca, seleziona +, tick Accedi con Apple__CAPGO_KEEP_0__ Configura per scegliere l'ID App principale.
- Registra la chiave e scarica il
.p8file. Apple ti consente di scaricarlo solo una volta. - Nota il ID Chiave e il tuo ID Team (mostrato nella parte superiore destra della porta di accesso per sviluppatori e sotto Abbonamenti).
Il tuo server utilizza la chiave per firmare il client_secret JWT che invia a Apple endpoint del token.
Registrare fonti email se invii email
Gli utenti possono scegliere Nascondi il mio indirizzo email, che ti fornisce un @privaterelay.appleid.com indirizzo. Apple invia solo posta da domini e indirizzi che hai registrato sotto Servizi > Accedi con Apple per la comunicazione via email. Saltare questo e i tuoi email di reimpostazione della password e ricevuta scompaiono silenziosamente.
Passo 2: Installa il plugin
bun add @capgo/capacitor-social-login
bunx cap sync
Il plugin raggruppa diversi provider. Se utilizzi solo Apple (e forse Google), disabilita gli altri in capacitor.config.ts così che i loro SDK non vengano spediti:
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;
Esegui bunx cap sync ancora dopo aver modificato questo blocco. Un provider disabilitato non è disponibile in esecuzione, quindi non disabilitare uno che utilizzi. Eliminare Facebook elimina anche le AD_ID avvertimenti di autorizzazione che il suo SDK attiva nel Google Play Console.
Passo 3: configurazione iOS
- Avvia
bunx cap open ios. - Scegli il App Xcode scrive Autenticazione & FunzionalitàXcode scrive Xcode scriveXcode scrive Accedi con Apple.
- Assicurati che l'identificatore del bundle corrisponda all'ID App che hai configurato.
Xcode scrive com.apple.developer.applesignin nel App.entitlements. Se costruisci in CI o con Capgo Costruisci, commetta il file di entitlements, altrimenti la costruzione cloud produce un'app che fallisce con ASAuthorizationError code 1000.
No Info.plist URL scheme o AppDelegate change is needed for Apple on iOS.
Passo 4: configurazione 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.
Aggiungi il collegamento profondo
In android/app/src/main/AndroidManifest.xml, all'interno dell' MainActivity <activity> elemento:
<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>
Invia l'intento al plugin
Modifica MainActivity.java così che il collegamento profondo raggiunge il plugin:
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);
}
}
Filtra sul host per mantenere funzionanti i tuoi altri collegamenti profondi (reset della password, link di marketing).
Il punto di fine callback
Apple invia code, id_token, state, e al primo accesso un user stringa JSON al tuo URL di ritorno. Il punto di fine ha scambiare il code e reindirizzare all'app con success=true insieme ai token, che è il contratto che il plugin elabora. Un esempio minimale di Hono utilizzando 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;
Il plugin legge i token dal parametro di query string del collegamento profondo, quindi qualsiasi cosa metti lì può finire nei log o essere letta da un altro app che registra lo stesso schema personalizzato. Invia solo i token di breve durata access_token Ecco id_tokenmai il token di refresh, verificare il id_token signature and nonce prima di fidarti di esso, utilizza uno schema univoco per la tua app (il tuo ID inverso del bundle).
Il client_secret JWT può vivere fino a sei mesi, ma generare uno breve per ogni richiesta evita un'interruzione di scadenza. Conserva il .p8 file nel tuo manager segreto, mai nel bundle dell'app.
Passo 5: Configurazione web
The web build uses Apple JS with the Services ID. Add the exact page URL that triggers the login to the Services ID return URLs. Apple does not accept localhost, quindi testa l'accesso web su un dominio di anteprima distribuito o attraverso un tunnel con un vero hostname.
Passo 6: Inizializza e accedi
Passo 6: Inizializzazione e login redirectUrl esclusivamente su Android e web. Su iOS, un non-empty redirectUrl rende il plugin POST il risultato a quella URL, che non desiderate con il flusso nativo.
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'
: '',
},
});
}
Poi attiva il login da un pulsante. Utilizza un nonce casuale affinché il tuo server possa rifiutare i token riprodotti:
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;
}
}
La risposta ti dà:
| Campo | Contenuto |
|---|---|
result.idToken |
JWT firmato da Apple, la cosa che il tuo server verifica |
result.profile.user |
ID utente stabile di Apple (lo stesso del sub claim) |
result.profile.email |
Indirizzo e-mail o indirizzo di relay privato, quando concesso |
result.profile.givenName / familyName |
Solo alla prima autorizzazione |
result.accessToken |
Con le impostazioni predefinite, questo contiene l'autorizzazione code, non un token di accesso reale |
result.authorizationCode |
Presente quando inizializzi con useProperTokenExchange: true |
Se il tuo server richiede token di rinnovo Apple (ad esempio per chiamare l'endpoint di revoca quando un utente cancella il proprio account), impostalo useProperTokenExchange: true in la apple config e scambiare authorizationCode su il server.
Passo 7: Verifica il token di identità sul tuo backend
Non fidarsi mai dei campi del profilo che il client invia. Verificare la firma JWT contro le chiavi pubbliche di Apple e controllare le dichiarazioni:
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',
};
}
Chiave il record utente su sube non su email. L'email può essere un indirizzo di relay e gli utenti possono fermare di condividerla. Dopo la verifica, emetti il tuo proprio token di sessione. Se utilizzi Supabase o Firebase, essi verificano il token per te, vedi Supabase con Capacitor Social Login.
Regole dell'App Store da tenere a mente
- Linea guida 4.8: se offri Google, Facebook o un altro sistema di login terzo, dovete offrire anche un'opzione focalizzata sulla privacy equivalente. Sign in with Apple qualifica.
- Linea guida 5.1.1(v)Le app che consentono agli utenti di creare un account devono consentire loro di cancellare l'account all'interno dell'app. Per gli utenti Sign in with Apple, chiamare Apple.
https://appleid.apple.com/auth/revokeGuida alla cancellazione dell'account Progettazione del pulsante. - Disegno del pulsanteUsa gli stili di bottone approvati da Apple e assicurati che il bottone sia almeno altrettanto prominente quanto gli altri pulsanti di accesso.
Troubleshooting
| Simulazione | Probabile causa | Soluzione |
|---|---|---|
ASAuthorizationError code 1000 su iOS |
Manca la capacità o l'entità nel target costruito | Aggiungi Sign in with Apple in Xcode, commit App.entitlements, regenera i profili |
| Code 1001 su iOS | L'utente ha annullato | Il plugin rifiuta con USER_CANCELLED, trattalo come un errore non rilevante |
invalid_client su Android o web |
Errato clientId (ID bundle utilizzato al posto di ID Servizi) o cattivo client_secret |
Utilizza l'ID Servizi, controlla l'ID Team, l'ID chiave e i contenuti della chiave |
invalid_request: Invalid redirect_uri |
L'URL di ritorno non è registrato esattamente | Aggiungi l'intero https:// URL dei Servizi ID, corrispondenza di virgole finali |
| L'app non torna dopo il login Android | L'intent filter schema o host differisce dal redirect del server | Confronta com.example.app://apple-login carattere per carattere |
apple.android.redirectUrl is null or empty |
No redirectUrl su Android |
Passa l'URL di callback HTTPS quando si inizializza su Android |
Nome e indirizzo e-mail sono null |
Non è la prima autorizzazione | Leggile dal tuo database. Revoca l'app nei impostazioni del dispositivo per testare nuovamente |
Token rifiutato dal backend: aud mancanza |
Server accetta solo un pubblico: | Accetta sia il bundle ID che l'ID Services: |
Correzioni di spedizione dopo la rilascio:
La maggior parte dei bug di autenticazione vive in TypeScript: un ID cliente sbagliato per piattaforma, un nonce mancante, una gestione degli errori cattiva. Quelle possono essere spedite agli utenti lo stesso giorno con aggiornamenti in tempo reale di Capgo senza attendere la revisione dell'app. Modifiche agli entità, la manifestazione Android o MainActivity senza dover attendere la revisione dell'app.
Cosa fare successivamente
- Aggiungi Google accanto ad Apple: How to sign in with Google using Capacitor.
- Seleziona la giusta combinazione di metodi per il tuo app: Come aggiungere l'autenticazione a un'app Capacitor.
- Riferimento del plugin e pagine delle piattaforme: Documentazione del login sociale e il Pagina di configurazione per iOS di Apple.