Saltar al contenido principal

Login con Apple en la App Móvil — Finnova

Sign in with Apple es el equivalente de Apple a Google Sign-In: permite al usuario autenticarse con su Apple ID. Finnova lo ofrece como método de login en iOS junto a Google y email/contraseña. Esta página define cómo se integra con el esquema de sesiones de doble token de Finnova (ver Control de Sesiones) y sigue el mismo patrón que el Login con Google.

Obligatorio en iOS: Apple exige (App Store Review Guideline 4.8) que cualquier app que ofrezca login social de terceros (como Google) también ofrezca Sign in with Apple. Por tanto, si Finnova incluye "Continuar con Google", debe incluir "Continuar con Apple" para ser aprobada en la App Store.


1. Principio clave

Igual que con Google, Sign in with Apple solo autentica la identidad, no emite las sesiones de Finnova. El flujo es:

  1. La app obtiene un Identity Token de Apple (JWT firmado por Apple) usando el SDK nativo.
  2. La app envía ese token al backend de Finnova.
  3. El backend verifica el token contra Apple, identifica o crea al usuario, y emite sus propios access + refresh tokens.

Las sesiones siguen siendo de Finnova (mismo esquema JWT de 15 min + refresh opaco de 30 días). Apple solo prueba "este usuario es quien dice ser".


2. Particularidades de Apple frente a Google

AspectoGoogleApple
Nombre y emailSiempre disponibles en cada loginSolo se entregan en el primer login — hay que persistirlos en ese momento
Ocultar emailNoEl usuario puede usar Hide My Email → recibe un alias @privaterelay.appleid.com
Identificador establesubsub (el campo sub del Identity Token)
PlataformasiOS, Android, WebiOS nativo; Android/Web vía flujo web OAuth

Crítico: Apple envía fullName y email únicamente en la primera autorización. En logins posteriores esos campos vienen vacíos. El backend debe guardar nombre y email la primera vez, o se perderán.


3. Configuración previa en Apple Developer

En Apple DeveloperCertificates, Identifiers & Profiles:

PasoDetalle
App IDHabilitar la capability Sign in with Apple en el App ID de Finnova
Service IDNecesario solo si se soporta el flujo web/Android (identifica al cliente OAuth)
KeyCrear una Sign in with Apple private key (.p8) para firmar/validar en el backend si se usa el flujo web
Bundle IDEl backend lo valida como audience del Identity Token

Para login nativo en iOS basta con habilitar la capability en el App ID; el audience a validar es el Bundle ID de la app.


4. Librería recomendada

StackLibrería
Expoexpo-apple-authentication
React Native (bare)@invertase/react-native-apple-authentication

Sign in with Apple solo está disponible en dispositivos iOS 13+. En Android, el botón debe ocultarse o usar el flujo web (Service ID). El componente nativo AppleAuthenticationButton cumple los lineamientos visuales que Apple exige.


5. Flujo de autenticación


6. Verificación del Identity Token en el backend

El backend valida el JWT de Apple contra las claves públicas publicadas en https://appleid.apple.com/auth/keys:

import { createRemoteJWKSet, jwtVerify } from 'jose';

const APPLE_JWKS = createRemoteJWKSet(
new URL('https://appleid.apple.com/auth/keys')
);

async function verifyAppleIdentityToken(identityToken: string) {
const { payload } = await jwtVerify(identityToken, APPLE_JWKS, {
issuer: 'https://appleid.apple.com',
audience: process.env.APPLE_BUNDLE_ID, // Bundle ID de la app
});

if (!payload.sub) throw new Error('Token de Apple inválido');

return {
appleSub: payload.sub as string, // ID estable y único del usuario en Apple
email: payload.email as string | undefined,
emailVerified: payload.email_verified === 'true' || payload.email_verified === true,
isPrivateEmail: payload.is_private_email === 'true', // alias de Hide My Email
};
}

La verificación comprueba la firma, el issuer (appleid.apple.com), el audience (nuestro Bundle ID) y la expiración.

Identificar al usuario por sub, no por email — sobre todo aquí, porque el email puede ser un alias de Hide My Email que el usuario puede desactivar.


7. Vinculación de cuentas (account linking)

Mismo criterio que Google, con un matiz por Hide My Email:

CasoAcción
apple_sub ya existeLogin directo a esa cuenta
apple_sub nuevo, pero el email real ya existe en una cuentaVincular: añadir apple_sub a la cuenta existente
Email es alias @privaterelay.appleid.comTratar como cuenta nueva (no se puede asumir que coincide con otra)
apple_sub y email ambos nuevosCrear cuenta nueva (sin contraseña)

Cambios al modelo de datos

ALTER TABLE users
ADD COLUMN apple_sub TEXT UNIQUE; -- NULL si nunca usó Apple

CREATE INDEX idx_users_apple_sub ON users(apple_sub);

Esto se suma a google_sub ya definido en el Login con Google. Un usuario puede tener ambos vinculados a la misma cuenta.


8. A partir de aquí: sesiones de Finnova

Una vez verificado el usuario, el flujo es idéntico al de email/contraseña y al de Google (ver Control de Sesiones):

  • Se crea una fila en sessions con la info del dispositivo.
  • Se emite el par access token (JWT 15 min) + refresh token (opaco 30 días).
  • El refresh token se guarda en SecureStore; el access token en memoria.
  • Aplican los mismos límites de sesiones por plan, revocación y detección de anomalías.

El POST /auth/apple reemplaza al POST /auth/login solo en la etapa de verificación de identidad. Todo lo demás no cambia.


9. Consideraciones de seguridad

  • Verificar siempre el token en el backend contra las claves públicas de Apple; nunca confiar en datos del cliente sin validar.
  • Validar el audience (Bundle ID) para rechazar tokens emitidos para otra app.
  • Persistir nombre y email en el primer login — Apple no los reenvía después.
  • No tratar el alias de Hide My Email como identificador — usar siempre sub.
  • No almacenar el Identity Token — se usa una vez y se descarta; lo que persiste son los tokens de Finnova.
  • Relay de email: si Finnova envía correos a un alias @privaterelay.appleid.com, debe configurar el dominio de envío en Apple Developer para que el relay los entregue.

10. Variables de entorno

APPLE_BUNDLE_ID=com.lyratech.finnova
# Solo si se soporta flujo web/Android:
APPLE_SERVICE_ID=com.lyratech.finnova.web
APPLE_TEAM_ID=XXXXXXXXXX
APPLE_KEY_ID=XXXXXXXXXX
APPLE_PRIVATE_KEY=<contenido del .p8>

Para login nativo en iOS basta con APPLE_BUNDLE_ID. Las demás variables solo aplican al flujo web/Android.


Referencias