Para agregar Sign in with Apple a una Capacitor aplicación, instale @capgo/capacitor-social-loginHabilite la capacidad de inicio de sesión con Apple en su ID de aplicación y en Xcode, llame SocialLogin.initialize({ apple: {} })Luego llame SocialLogin.login({ provider: 'apple' })On iOS abre la hoja nativa de Apple y devuelve un token de identidad. Android y web utilizan el flujo OAuth web de Apple, que requiere un ID de Servicio y una URL de redirección HTTPS.
Esta guía cubre la configuración del desarrollador de Apple, el cliente code para iOS, Android y web, la verificación del token en su servidor de backend, y los errores que es probable que encuentre. Se dirige a Capacitor 8 y Xcode 26, que App Store Connect ha requerido para subir desde abril de 2026.
¿Qué necesitas antes de empezar
| Requisito | ¿Por qué |
|---|---|
| Membresía del programa de desarrolladores de Apple pagado | App IDs, IDs de Servicios y claves de inicio de sesión con Apple solo están disponibles para miembros |
| Un Mac con Xcode 26 | Se necesita para crear y subir aplicaciones de iOS en 2026 |
| Capacitor 8 proyecto | El plugin v8 apunta a Capacitor 8 |
| Un dominio HTTPS que controlas | Sólo para Android y web. Apple se niega localhost y URLs de retorno HTTP planos |
| Punto de conexión de servidor | Siempre para verificar tokens. En Android también recibe la llamada de Apple |
Si no tiene certificados de firma aún, el generador de certificado de iOS los crea sin bailar con la llave de Mac.
Sobre cómo funciona Sign in with Apple en cada plataforma
Apple solo envía un nativo SDK para sus propias plataformas, por lo que el flujo difiere:
| Plataforma | Mecanismo | ID de cliente utilizado | Requerimiento de backend para inicio de sesión |
|---|---|---|---|
| iOS | Nativo ASAuthorizationAppleIDProvider sheet |
ventana emergente | No |
| No | OAuth de Apple en una pestaña personalizada de Chrome, Apple envía a tu servidor, tu servidor redirige de vuelta a la aplicación. | ID de Servicios | Yes |
| Sí | Apple popup de JS | Servicios ID | No (pero verifica el token) |
La audiencia (aud) del token de identidad sigue el ID de cliente. Los tokens de iOS llevan tu ID de paquete, los tokens de Android y web llevan el Servicios ID. Tu backend tiene que aceptar ambos.
Configura el paso 1: Cuenta del desarrollador de Apple
Configura el paso 1: Cuenta del desarrollador de Apple
- Open Certificados, Identificadores y Perfiles y seleccionar y selecciona.
- Abra el ID de la aplicación que coincide con su Capacitor.
appId(por ejemplo)com.example.app). Create it if it does not exist. - Bajo Capacidades, marca Iniciar sesión con Apple y guarda. Deja que sea un ID de App principal a menos que agrupes varias aplicaciones.
Si cambias las capacidades en un ID de App existente, regenera los perfiles de configuración que lo utilizan. Los perfiles antiguos no incluyen la nueva autorización.
Crea un ID de Servicios (solo para Android y web)
- En Identificadores, haz clic + y selecciona IDs de Servicios.
- Utilice una descripción como “Iniciar sesión en la web de ejemplo” y un identificador como
com.example.app.signinEste valor se convierte en elclientIden Android y web. - Abra el nuevo ID de Servicio, marque Iniciar sesión con Apple, y haga clic en Configurar.
- Seleccione su ID de App como ID de App principal.
- Agregue su dominio bajo Dominios y Subdominios (sin esquema, por ejemplo
auth.example.com). - Agregar las URLs de retorno debajo de URLs de retorno con la ruta completa, por ejemplo
https://Para inicio de sesión web, agregue la URL de la página que llama al inicio de sesión, con y sin una barra diagonal de cierre.https://auth.example.com/apple/callbackCrear una clave de inicio de sesión con Apple (token de Android y servidor de intercambio)
Crear una clave de inicio de sesión con Apple (intercambio de tokens Android y servidor)
- Claves , hacer clic, seleccionar +, tick , y hacer clicy haz clic Configura para elegir el ID de App principal.
- Registra la clave y descarga el
.p8archivo. Apple solo te permite descargarlo una vez. - Toma nota del ID de clave y tu ID de equipo (mostrado en la parte superior derecha de la consola de desarrolladores y bajo Miembros).
Tu servidor utiliza la clave para firmar el client_secret JWT que envía a la API de tokens de Apple.
Register email sources if you send email
Los usuarios pueden elegir Ocultar mi correo electrónico, lo que te da una @privaterelay.appleid.com dirección. Solo Apple envía correo electrónico de dominios y direcciones que has registrado. Servicios > Iniciar sesión con Apple para la comunicación por correo electrónicoSilenciosamente desaparecen tus correos electrónicos de restablecimiento de contraseña y facturas.
Step 2: Instala el plugin
bun add @capgo/capacitor-social-login
bunx cap sync
El plugin agrupa varios proveedores. Si solo usas Apple (y tal vez Google), desactiva los demás en capacitor.config.ts para que sus SDKs no se envíen:
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;
Corre bunx cap sync No vuelvas a cambiar esta bloque. Un proveedor deshabilitado no está disponible en tiempo de ejecución, así que no lo deshabiles. Eliminar Facebook también elimina el AD_ID advertencias de permiso que su SDK desencadena en el Console de Google Play.
Step 3: configuración de iOS
- Correr
bunx cap open ios. - Selecciona el App Aplicación Iniciar sesión & Capabilities, click haz clic en+ Capacidad Iniciar sesión con Apple.
- Asegúrese de que el identificador de la cesta coincida con el ID de la App que configuró.
Xcode escribe com.apple.developer.applesignin en App.entitlementsSi construyes en CI o con Capgo Construcción, comite el archivo de entitlements, de lo contrario, la compilación en la nube produce una aplicación que falla con ASAuthorizationError code 1000.
en Info.plist URL scheme o AppDelegate Se necesita cambio para Apple en iOS.
Paso 4: configuración de 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.
Agregar el enlace profundo
In android/app/src/main/AndroidManifest.xml, dentro de 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>
Reenvía la intención al plugin
Editar MainActivity.java para que el enlace profundo llegue al 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);
}
}
El filtrado en el host mantiene funcionando tus otros enlaces profundos (restablecimiento de contraseña, enlaces de marketing).
El punto final de llamada
Apple publica code, id_token, state, y en el primer inicio un user cadena JSON a tu URL de retorno. El punto final tiene que intercambiar el code y redirigir al app con success=true más los tokens, que es el contrato que el plugin parsea. Un ejemplo mínimo de Hono utilizando 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;
El plugin lee los tokens del enlace profundo de la cadena de consulta, por lo que cualquier cosa que pongas allí puede terminar en registros o ser leída por otra app que registre el mismo esquema personalizado. Envía solo los tokens de vida corta access_token y id_tokennunca el token de refresco, verifica la id_token signature and nonce en tu servidor antes de confiar en él, y utiliza un esquema único para tu aplicación (tu ID de paquete inverso).
La client_secret JWT puede vivir hasta seis meses, pero generar una breve por solicitud evita una interrupción de caducidad. Mantén el .p8 archivo en tu administrador de secretos, nunca en el paquete de la aplicación.
Paso 5: Configuración web
La construcción web utiliza Apple JS con el ID de Servicios. Agrega la URL de página exacta que desencadena la autenticación a las URL de retorno del ID de Servicios. Apple no acepta localhost, así que prueba el inicio de sesión web en un dominio de previsualización desplegado o a través de un túnel con un hostname real.
Paso 6: Inicializar y autenticar
Inicializa una vez al inicio de la aplicación. Pasa el redirectUrl solamente en Android y web. En iOS, un no vacío redirectUrl hace que el plugin envíe el resultado a esa URL, lo que no deseas con el flujo 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'
: '',
},
});
}
Después, activa el inicio de sesión desde un botón. Utiliza un nonce aleatorio para que tu servidor pueda rechazar tokens repetidos:
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 respuesta te da:
| Campo | Contenido |
|---|---|
result.idToken |
JWT firmado de Apple, la cosa que tu servidor verifica |
result.profile.user |
ID de usuario estable de Apple (igual que el sub reclamo) |
result.profile.email |
Correo electrónico o dirección de relay privada, cuando se concede |
result.profile.givenName / familyName |
Sólo en la primera autorización |
result.accessToken |
Con los ajustes por defecto, esto contiene la autorización code, no un token de acceso real |
result.authorizationCode |
Presente cuando inicies con useProperTokenExchange: true |
Si su servidor necesita tokens de actualización de Apple (por ejemplo, para llamar al endpoint de revocación cuando un usuario elimina su cuenta), establezca useProperTokenExchange: true en la apple configuración y cancele authorizationCode en el servidor.
Step 7: Verifique el token de identidad en su backend
Nunca confíes en los campos de perfil que el cliente envía. Verifica la firma del JWT contra las llaves públicas de Apple y revisa las declaraciones.
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',
};
}
Registre el registro del usuario 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 con Capacitor Inicio de sesión social.
Supabase con __CAPGO_KEEP_0__ Inicio de sesión social
- Directiva 4.8: si ofrece Google, Facebook o otra opción de inicio de sesión de terceros como el sistema de cuenta principal, debe ofrecer también una opción equivalente enfocada en la privacidad. Iniciar sesión con Apple cumple con esto.
- Directiva 5.1.1(v)Las aplicaciones que permiten a los usuarios crear cuentas deben permitirles eliminar la cuenta en la aplicación. Para los usuarios de Sign in with Apple, llame a Apple's
https://appleid.apple.com/auth/revokeGuía de eliminación de cuentas Guía de eliminación de cuenta. - Diseño de botónUtilice los estilos de botón aprobados por Apple y haga que el botón sea tan destacado como otros botones de inicio de sesión.
Troubleshooting
| Síntoma | Causa probable | Solución |
|---|---|---|
ASAuthorizationError code 1000 en iOS |
Falta de capacidad o permiso en el objetivo compilado | Agregar Sign in with Apple en Xcode, hacer commit App.entitlementsregenerar perfiles |
| Code 1001 en iOS | El usuario canceló | El plugin rechaza con USER_CANCELLED, tratar como un error no |
invalid_client en Android o web |
Incorrecto clientId (ID de paquete utilizado en lugar de ID de Servicios) o mal client_secret |
Usar el ID de Servicios, verificar ID de equipo, ID de clave y contenido de la clave |
invalid_request: Invalid redirect_uri |
La URL de retorno no está registrada exactamente | Agregar el identificador de Apple https:// La aplicación no regresa después de iniciar sesión en Android |
| La aplicación no regresa después de iniciar sesión con Android | Intent filter scheme or host differs from the server redirect | Compare com.example.app://apple-login carácter por carácter |
apple.android.redirectUrl is null or empty |
en Android redirectUrl en Android |
Pase su URL de llamada HTTPS cuando inicie en Android |
No es la primera autorización null |
No es la primera autorización | Lee las desde tu base de datos. Revoca la aplicación en ajustes de dispositivo para probar de nuevo. |
Token rechazado por el servidor: aud incompatibilidad |
El servidor solo acepta un audiencia | Aceptar ambos ID de paquete y ID de Servicios |
Arreglos de envío después de la liberación
La mayoría de los problemas de autenticación viven en TypeScript: una ID de cliente equivocada por plataforma, un nonce faltante, una mala gestión de errores. Pueden ser enviados a los usuarios el mismo día con actualizaciones en vivo de Capgo sin esperar la revisión de la App. Los cambios en las entidades, el manifiesto de Android o MainActivity son nativos y requieren una nueva compilación de tienda.
¿Dónde ir a continuación?
- Agregar Google junto a Apple: Cómo iniciar sesión con Google utilizando Capacitor.
- Elige la mezcla adecuada de métodos para tu aplicación: Cómo agregar autenticación a una aplicación Capacitor.
- Referencia del plugin y páginas de plataforma: Documentación de inicio de sesión social y el Página de configuración de iOS de Apple.