Passer à la navigation principale

Comment se connecter avec Apple en utilisant Capacitor (2026)

Add Sign in with Apple to a Capacitor 8 app on iOS, Android, and web: Apple Developer setup, plugin code, backend token checks, and common errors.

Crédits de l'article

Martin Donadieu

Auteur

Valeria

Relecteur

Jordan

Éditeur

Comment se connecter avec Apple en utilisant Capacitor (2026)

To add Sign in with Apple to a Capacitor app, install @capgo/capacitor-social-login, activez la capacité Sign in with Apple sur votre ID d'application et dans Xcode, appelez SocialLogin.initialize({ apple: {} })Alors, appelez. SocialLogin.login({ provider: 'apple' })Sur iOS, cela ouvre la feuille native d'Apple et renvoie un jeton d'identité. Android et web utilisent la flotte OAuth web d'Apple, qui nécessite un ID de service et une URL de redirection HTTPS.

Cette guide couvre la configuration du développeur Apple, le client code pour iOS, Android et web, la vérification du jeton sur votre serveur backend, et les erreurs auxquelles vous êtes le plus susceptible de tomber. Il s'adresse à Capacitor 8 et Xcode 26, qui App Store Connect a exigé pour les téléchargements depuis avril 2026.

Ce que vous avez besoin avant de commencer

Exigence Pourquoi
Membre du programme de développeur Apple payant Les App IDs, les Services IDs et les clés Sign in with Apple sont uniquement disponibles pour les membres.
Un Mac avec Xcode 26 Needed to build and upload iOS apps in 2026
Capacitor 8 projet Plugin v8 cible Capacitor 8
Un domaine HTTPS que vous contrôlez Seulement pour Android et web. Apple refuse localhost les URLs HTTP sans sécurisation retournent
Un point de terminaison backend Toujours pour vérifier les jetons. Sur Android, il reçoit également l'appel de rappel d'Apple

Si vous n'avez pas encore de certificats de signature, le générateur de certificat iOS les crée sans danser avec la cléchainée de Mac.

Comment Sign in with Apple fonctionne sur chaque plateforme

Apple ne fournit qu'un SDK natif pour ses propres plateformes, donc le flux diffère :

Plateforme Mécanisme Identifiant client utilisé Backend requis pour le connexion
IOS Natif ASAuthorizationAppleIDProvider sheet Feuille de dialogue No
Non OAuth Apple dans une fenêtre personnalisée de Chrome, Apple envoie un message à votre serveur, votre serveur redirige ensuite vers l'application. ID de services Yes
Oui Apple popup JS ID de service Non (mais vérifiez le jeton)

Le public (aud) du jeton d'identité suit l'ID de client. Les jetons de iOS portent votre ID de bundle, les jetons d'Android et web portent l'ID de service. Votre serveur doit accepter les deux.

Étape 1 : Configurez le compte développeur Apple

Activer la capacité sur votre ID d'application

  1. Ouvrir Certificats, identifiants et profils et sélectionner Identifiants.
  2. Ouvrir l'ID d'application correspondant à votre Capacitor appId (par exemple) com.example.appCréez-le si ce n'est pas déjà le cas.
  3. Sous Capacités, cochez Se connecter avec Apple Laissez-le comme ID d'application principal à moins que vous ne groupiez plusieurs applications.

Si vous modifiez les capacités d'un ID d'application existant, régénérez les profils de provisionnement qui l'utilisent. Les anciens profils ne comprennent pas la nouvelle autorisation.

Créez un ID de service (seulement Android et web)

  1. Dans Identifiants, cliquez + et choisissez Identifiants de Services.
  2. Utilisez une description comme « Exemple de connexion Web » et un identifiant comme com.example.app.signinCet identifiant devient le clientId sur Android et web.
  3. Ouvrez le nouveau Identifiant de Service, cochez Se connecter avec Apple, et cliquez sur Configurer.
  4. Pick your App ID as the primary App ID.
  5. Ajoutez votre domaine sous Domaines et sous-domaines (pas de schéma, par exemple) auth.example.com).
  6. Ajoutez les URL de retour sous URL de retour avec le chemin complet, par exemple https:// chemin, par exemple https://auth.example.com/apple/callbackPour les logins web, ajoutez l'URL de la page qui appelle la connexion, avec et sans antislash.

Créez une clé Sign in with Apple (échange de jeton Android et serveur)

  1. Clés , cliquez, cochez +, tick Se connecter avec Apple, et cliquez Configurez pour choisir l'ID d'application principal.
  2. Enregistrez la clé et téléchargez le .p8 fichier. Apple vous permet de le télécharger qu'une seule fois.
  3. Notez le ID de clé et votre ID d'équipe affiché en haut à droite de la plateforme de développeurs et sous Abonnements).

Votre serveur utilise la clé pour signer le client_secret JWT qu'il envoie à l'endpoint de jeton d'Apple.

Register email sources if you send email

Les utilisateurs peuvent choisir. Cacher mon adresse e-mail.laquelle vous obtenez un @privaterelay.appleid.com Services > Connexion avec Apple pour la communication par courriel. Services > Se connecter avec Apple pour la communication par e-mailVos emails de réinitialisation de mot de passe et de facture disparaissent discrètement.

Étape 2 : Installez le plugin

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

Le plugin rassemble plusieurs fournisseurs. Si vous utilisez uniquement Apple (et peut-être Google), désactivez les autres. capacitor.config.ts Ainsi, leurs SDK ne sont pas embarqués.

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 Ne réactivez pas ce bloc après y avoir apporté des modifications. Un fournisseur désactivé n'est pas disponible en temps réel, ne le désactivez donc pas. AD_ID les avertissements de permission que son SDK déclenche dans le console Google Play.

Étape 3 : configuration iOS

  1. Exécutez bunx cap open ios.
  2. Sélectionnez le App ouverture Authentification et capacités, cliquez sur + Capacité, et ajoutez Authentification avec Apple.
  3. Assurez-vous que l'identifiant du bundle correspond à l'ID d'application que vous avez configuré.

Xcode écrit com.apple.developer.applesignin à App.entitlements. Si vous construisez en CI ou avec Capgo Construire, commitez ensuite le fichier d'entitlements, sinon la build cloud produit une application qui échoue avec ASAuthorizationError code 1000.

No Info.plist URL scheme ou AppDelegate Une modification est nécessaire pour Apple sur iOS.

Étape 4 : Configuration 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.

Dans android/app/src/main/AndroidManifest.xmlà l'intérieur de l' MainActivity <activity> élément :

<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>

Transmettre l'intention au plugin

Modifier MainActivity.java afin que le lien profond atteigne le 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);
    }
}

Filtrage sur l'hôte conserve vos autres liens profonds (réinitialisation de mot de passe, liens marketing) en fonction.

L'endpoint de rappel

Apple envoie code, id_token, state, et lors de la première connexion un user chaîne JSON à votre URL de retour. L'endpoint doit échanger le code et rediriger vers l'application avec success=true plus les jetons, ce qui constitue le contrat que le plugin analyse. Un exemple minimal Hono utilisant 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;

Le plugin lit les jetons à partir de la chaîne de requête de lien profond, donc tout ce que vous y mettez peut finir dans les journaux ou être lu par une autre application qui s'inscrit au même schéma personnalisé. Envoyez uniquement les jetons à durée de vie courte access_token et id_tokenne jamais le jeton de rafraîchissement, vérifiez la id_token signature et nonce sur votre serveur avant de le faire confiance, et utilisez un schéma unique à votre application (votre ID de bundle inversé).

Le client_secret JWT peut durer jusqu'à six mois, mais générer un court par demande évite une panne de expiration. Gardez le .p8 ne jamais y mettre dans le bundle de l'application.

Étape 5 : Configuration web

Le build web utilise Apple JS avec l'ID de Services. Ajoutez l'URL de page exacte qui déclenche la connexion à l'ID de retour de Services. Apple ne prend pas localhost, testez ensuite l'authentification web sur un domaine de prévisualisation déployé ou via un tunnel avec un vrai nom de domaine.

Étape 6 : Initialisation et connexion

Initialize once at app start. Pass the redirectUrl seul sur Android et sur le web. Sur iOS, un non-vide redirectUrl fait que le plugin envoie le résultat à cette URL, ce que vous ne voulez pas avec le flux natif.

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'
            : '',
    },
  });
}

Puis déclenchez la connexion à partir d'un bouton. Utilisez un nonce aléatoire afin que votre serveur puisse rejeter les jetons réutilisés :

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 réponse vous donne :

Champ Contenu
result.idToken JWT signé par Apple, la chose que votre serveur vérifie
result.profile.user ID utilisateur stable d'Apple (même que l') sub revendication)
result.profile.email Adresse e-mail ou adresse de relais privée, lorsque concédée
result.profile.givenName / familyName Seul au premier autorisation
result.accessToken With les paramètres par défaut, cela contient l'autorisation code, pas un vrai jeton d'accès
result.authorizationCode Présent lors de l'initialisation avec useProperTokenExchange: true

Si votre serveur nécessite les jetons de rafraîchissement Apple (par exemple pour appeler l'endpoint de révocation lorsque l'utilisateur supprime son compte), définissez useProperTokenExchange: true dans la apple config et échangez authorizationCode sur le serveur.

Étape 7 : Vérifiez le jeton d'identité sur votre serveur

N'ayez jamais confiance dans les champs de profil envoyés par le client. Vérifiez la signature JWT contre les clés publiques d'Apple et vérifiez les revendications :

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',
  };
}

Enregistrez le dossier utilisateur sub, not on email. The email can be a relay address, and users can stop sharing it. After verification, issue your own session token. If you use Supabase or Firebase, they verify the token for you, see Supabase avec Capacitor Authentification sociale.

Supabase avec __CAPGO_KEEP_0__ Authentification sociale

  • Directive 4.8: Si vous proposez Google, Facebook ou un autre système de connexion tiers comme système de compte principal, vous devez également proposer une option équivalente axée sur la vie privée. Sign in with Apple qualifie.
  • Directive 5.1.1(v): apps that let users create accounts must let them delete the account in the app. For Sign in with Apple users, call Apple’s https://appleid.apple.com/auth/revoke endpoint de suppression. Consultez notre guide de suppression de compte.
  • Résolution des problèmesUtilisez les styles d'appui d'Apple approuvés et faites en sorte que le bouton soit aussi visible que les autres boutons de connexion.

Troubleshooting

Symptôme Probable cause Solution
ASAuthorizationError code 1000 sur iOS Manque de capacité ou de droits dans la cible construite Ajoutez Sign in with Apple dans Xcode, puis commitez App.entitlements, régénérez les profils
Code 1001 sur iOS L'utilisateur a annulé Le plugin rejette avec USER_CANCELLED, traitez comme une erreur non
invalid_client sur Android ou web Erreur clientId ID de bundle (utiliser au lieu de ID de Services) ou mauvais. client_secret Utilisez l'ID de service, vérifiez l'ID d'équipe, l'ID de clé et les contenus de clé
invalid_request: Invalid redirect_uri L'URL de retour n'est pas enregistrée exactement Ajoutez le https:// URL vers l'ID des Services, correspondre aux antislashes
L'application ne revient pas après un login Android Le schéma ou l'hôte de la filtre d'intention diffère du redirigeant du serveur. Comparer com.example.app://apple-login caractère par caractère
apple.android.redirectUrl is null or empty Non redirectUrl sur Android Fournissez votre URL de rappel HTTPS lors de l'initialisation sur Android
Nom et adresse e-mail sont null Pas la première autorisation Lisez-les dans votre base de données. Révoquez l'application dans les paramètres du appareil pour tester à nouveau.
Le jeton a été rejeté par le serveur. aud mismatch Le serveur ne prend en compte qu'une seule audience. Acceptez les deux identifiants de bundle et Services ID

Corrections après la mise en production

La plupart des bugs d'authentification vivent dans TypeScript : un ID client incorrect par plateforme, un nonce manquant, une mauvaise gestion des erreurs. Ces problèmes peuvent être livrés aux utilisateurs le même jour avec Capgo mises à jour en direct Sans attendre l'examen d'App Store. Les modifications des droits, le manifeste Android ou MainActivity are native and need a new store build.

Où aller ensuite

Actualisations en direct pour les applications Capacitor

When a web-layer bug is live, ship the fix through Capgo instead of waiting days for app store approval. Users get the update in the background while native changes stay in the normal review path.

Dernières actualités

Capgo gives you the best insights you need to create a truly professional mobile app.