Autentica con Apple

Puedes permitir que los usuarios se autentiquen con Firebase mediante su ID de Apple usando el SDK de Firebase para completar el flujo de acceso de OAuth 2.0 de extremo a extremo.

Antes de comenzar

Para que los usuarios accedan con Apple, primero configura la función Iniciar sesión con Apple en el sitio para desarrolladores de Apple y habilita Apple como proveedor de acceso para tu proyecto de Firebase.

Únete al Programa para desarrolladores de Apple

Solo los miembros del Programa para desarrolladores de Apple pueden configurar la función Iniciar Sesión con Apple.

Configura la función de acceso con Apple

  1. Habilita el acceso con Apple para tu app en la página Certificados, identificadores y perfiles del sitio para desarrolladores de Apple.
  2. Asocia tu sitio web a tu app como se describe en la primera sección de la página sobre cómo configurar el acceso con Apple para la Web. Registra la siguiente URL como una URL de retorno cuando se te solicite que lo hagas:
    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
    Puedes obtener el ID de tu proyecto de Firebase en la página de configuración de Firebase console. Cuando hayas terminado, toma nota de tu ID de servicio nuevo, ya que lo necesitarás en la siguiente sección.
  3. Crea una clave privada para acceder con Apple. Necesitarás tu ID de clave y tu clave privada nueva en la siguiente sección.
  4. Si usas alguna de las características de Firebase Authentication que envía correos electrónicos a los usuarios (como el acceso con vínculo por correo electrónico, la verificación de dirección de correo electrónico o la revocación de cambio de cuenta, entre otras), configura el servicio privado de retransmisión de correo electrónico de Apple y registra noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (o tu dominio de plantilla de correo electrónico personalizado) para que Apple pueda transmitir los correos electrónicos que envía Firebase Authentication a direcciones de correo electrónico anónimas de Apple.

Habilita Apple como proveedor de acceso

  1. Agrega Firebase a tu proyecto de Apple. Asegúrate de registrar el ID del paquete de tu app cuando la configures en Firebase console.
  2. En Firebase console, abre la sección Authentication. En la pestaña Método de acceso, habilita el proveedor Apple. Especifica el ID de servicio que creaste en la sección anterior. Además, en la sección de configuración de flujo de código OAuth, especifica tu ID de equipo de Apple junto con la clave privada y el ID de clave que creaste en la sección anterior.

Cumple con los requisitos de datos anonimizados de Apple

La función Iniciar sesión con Apple les ofrece a los usuarios la opción de anonimizar sus datos, incluida su dirección de correo electrónico, durante el acceso. Los usuarios que eligen esta opción tienen direcciones de correo electrónico con el dominio privaterelay.appleid.com. Cuando usas Iniciar sesión con Apple en tu app, debes tratar estos IDs de Apple anonimizados según las políticas para desarrolladores o las condiciones de Apple aplicables.

Esto incluye obtener los consentimientos del usuario correspondientes antes de asociar cualquier información personal que lo identifique directamente con un ID de Apple anonimizado. Cuando usas Firebase Authentication, es posible que se incluyan las siguientes acciones:

  • Vincular una dirección de correo electrónico a un ID de Apple anonimizado o viceversa
  • Vincular un número de teléfono a un ID de Apple anonimizado o viceversa
  • Vincular una credencial de redes sociales no anónima (Facebook, Google, etc.) a un ID de Apple anonimizado o viceversa

Esta lista no es exhaustiva. Consulta el Acuerdo de licencia del Programa para desarrolladores de Apple en la sección Membresía de tu cuenta de desarrollador para asegurarte de que tu app cumpla con los requisitos de Apple.

Accede con Apple y autentica con Firebase

Para autenticar con una cuenta de Apple, primero debes hacer que el usuario acceda a su cuenta de Apple con el framework AuthenticationServices de Apple y, luego, usar el token de ID de la respuesta de Apple para crear un objeto AuthCredential de Firebase:

  1. Para cada solicitud de acceso, genera una string aleatoria, un “nonce”, que utilizarás a fin de asegurarte de que se otorgó el token de ID que obtienes específicamente en respuesta a la solicitud de autenticación de tu app. Este paso es importante para evitar ataques de repetición.

    Puedes generar un nonce criptográficamente seguro con SecRandomCopyBytes(_:_:_), como en el siguiente ejemplo:

    Swift

    private func randomNonceString(length: Int = 32) -> String {
      precondition(length > 0)
      var randomBytes = [UInt8](repeating: 0, count: length)
      let errorCode = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes)
      if errorCode != errSecSuccess {
        fatalError(
          "Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)"
        )
      }
    
      let charset: [Character] =
        Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
    
      let nonce = randomBytes.map { byte in
        // Pick a random character from the set, wrapping around if needed.
        charset[Int(byte) % charset.count]
      }
    
      return String(nonce)
    }
    
        

    Objective‑C

    // Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
    - (NSString *)randomNonce:(NSInteger)length {
      NSAssert(length > 0, @"Expected nonce to have positive length");
      NSString *characterSet = @"0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._";
      NSMutableString *result = [NSMutableString string];
      NSInteger remainingLength = length;
    
      while (remainingLength > 0) {
        NSMutableArray *randoms = [NSMutableArray arrayWithCapacity:16];
        for (NSInteger i = 0; i < 16; i++) {
          uint8_t random = 0;
          int errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random);
          NSAssert(errorCode == errSecSuccess, @"Unable to generate nonce: OSStatus %i", errorCode);
    
          [randoms addObject:@(random)];
        }
    
        for (NSNumber *random in randoms) {
          if (remainingLength == 0) {
            break;
          }
    
          if (random.unsignedIntValue < characterSet.length) {
            unichar character = [characterSet characterAtIndex:random.unsignedIntValue];
            [result appendFormat:@"%C", character];
            remainingLength--;
          }
        }
      }
    
      return [result copy];
    }
        

    Enviarás el hash SHA256 del nonce con tu solicitud de acceso, que Apple pasará sin cambios en la respuesta. Para validar la respuesta, Firebase genera un hash del nonce original y lo compara con el valor que pasó Apple.

    Swift

    @available(iOS 13, *)
    private func sha256(_ input: String) -> String {
      let inputData = Data(input.utf8)
      let hashedData = SHA256.hash(data: inputData)
      let hashString = hashedData.compactMap {
        String(format: "%02x", $0)
      }.joined()
    
      return hashString
    }
    
        

    Objective‑C

    - (NSString *)stringBySha256HashingString:(NSString *)input {
      const char *string = [input UTF8String];
      unsigned char result[CC_SHA256_DIGEST_LENGTH];
      CC_SHA256(string, (CC_LONG)strlen(string), result);
    
      NSMutableString *hashed = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
      for (NSInteger i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
        [hashed appendFormat:@"%02x", result[i]];
      }
      return hashed;
    }
        
  2. Inicia el flujo de acceso de Apple. Para ello, incluye en tu solicitud el hash SHA256 del nonce y la clase delegada que controlará la respuesta de Apple (consulta el siguiente paso):

    Swift

    import CryptoKit
    
    // Unhashed nonce.
    fileprivate var currentNonce: String?
    
    @available(iOS 13, *)
    func startSignInWithAppleFlow() {
      let nonce = randomNonceString()
      currentNonce = nonce
      let appleIDProvider = ASAuthorizationAppleIDProvider()
      let request = appleIDProvider.createRequest()
      request.requestedScopes = [.fullName, .email]
      request.nonce = sha256(nonce)
    
      let authorizationController = ASAuthorizationController(authorizationRequests: [request])
      authorizationController.delegate = self
      authorizationController.presentationContextProvider = self
      authorizationController.performRequests()
    }
    

    Objective‑C

    @import CommonCrypto;
    
    - (void)startSignInWithAppleFlow {
      NSString *nonce = [self randomNonce:32];
      self.currentNonce = nonce;
      ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
      ASAuthorizationAppleIDRequest *request = [appleIDProvider createRequest];
      request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
      request.nonce = [self stringBySha256HashingString:nonce];
    
      ASAuthorizationController *authorizationController =
          [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
      authorizationController.delegate = self;
      authorizationController.presentationContextProvider = self;
      [authorizationController performRequests];
    }
    
  3. Controla la respuesta de Apple en tu implementación de ASAuthorizationControllerDelegate. Si el acceso fue exitoso, usa el token de ID de la respuesta de Apple con el nonce sin hash para autenticar con Firebase:

    Swift

    @available(iOS 13.0, *)
    extension MainViewController: ASAuthorizationControllerDelegate {
    
      func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
          guard let nonce = currentNonce else {
            fatalError("Invalid state: A login callback was received, but no login request was sent.")
          }
          guard let appleIDToken = appleIDCredential.identityToken else {
            print("Unable to fetch identity token")
            return
          }
          guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
            print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
            return
          }
          // Initialize a Firebase credential, including the user's full name.
          let credential = OAuthProvider.appleCredential(withIDToken: idTokenString,
                                                            rawNonce: nonce,
                                                            fullName: appleIDCredential.fullName)
          // Sign in with Firebase.
          Auth.auth().signIn(with: credential) { (authResult, error) in
            if error {
              // Error. If error.code == .MissingOrInvalidNonce, make sure
              // you're sending the SHA256-hashed nonce as a hex string with
              // your request to Apple.
              print(error.localizedDescription)
              return
            }
            // User is signed in to Firebase with Apple.
            // ...
          }
        }
      }
    
      func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        // Handle error.
        print("Sign in with Apple errored: \(error)")
      }
    
    }
    

    Objective‑C

    - (void)authorizationController:(ASAuthorizationController *)controller
       didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) {
      if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
        ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
        NSString *rawNonce = self.currentNonce;
        NSAssert(rawNonce != nil, @"Invalid state: A login callback was received, but no login request was sent.");
    
        if (appleIDCredential.identityToken == nil) {
          NSLog(@"Unable to fetch identity token.");
          return;
        }
    
        NSString *idToken = [[NSString alloc] initWithData:appleIDCredential.identityToken
                                                  encoding:NSUTF8StringEncoding];
        if (idToken == nil) {
          NSLog(@"Unable to serialize id token from data: %@", appleIDCredential.identityToken);
        }
    
        // Initialize a Firebase credential, including the user's full name.
        FIROAuthCredential *credential = [FIROAuthProvider appleCredentialWithIDToken:IDToken
                                                                             rawNonce:self.appleRawNonce
                                                                             fullName:appleIDCredential.fullName];
    
        // Sign in with Firebase.
        [[FIRAuth auth] signInWithCredential:credential
                                  completion:^(FIRAuthDataResult * _Nullable authResult,
                                               NSError * _Nullable error) {
          if (error != nil) {
            // Error. If error.code == FIRAuthErrorCodeMissingOrInvalidNonce,
            // make sure you're sending the SHA256-hashed nonce as a hex string
            // with your request to Apple.
            return;
          }
          // Sign-in succeeded!
        }];
      }
    }
    
    - (void)authorizationController:(ASAuthorizationController *)controller
               didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) {
      NSLog(@"Sign in with Apple errored: %@", error);
    }
    

A diferencia de otros proveedores compatibles con Firebase Auth, Apple no proporciona una URL de foto.

Además, cuando el usuario elige no compartir su correo electrónico con la app, Apple aprovisiona una dirección de correo electrónico única para ese usuario (con formato xyz@privaterelay.appleid.com), que comparte con tu app. Si configuraste el servicio privado de retransmisión de correo electrónico, Apple reenvía a la dirección de correo real del usuario los correos electrónicos enviados a la dirección anonimizada.

Reautenticación y vinculación de cuentas

Se puede usar el mismo patrón con reauthenticateWithCredential(), que puedes usar a fin de recuperar una credencial nueva para operaciones confidenciales que requieren un acceso reciente:

Swift

// Initialize a fresh Apple credential with Firebase.
let credential = OAuthProvider.credential(
  withProviderID: "apple.com",
  IDToken: appleIdToken,
  rawNonce: rawNonce
)
// Reauthenticate current Apple user with fresh Apple credential.
Auth.auth().currentUser.reauthenticate(with: credential) { (authResult, error) in
  guard error != nil else { return }
  // Apple user successfully re-authenticated.
  // ...
}

Objective‑C

FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com",
                                                                   IDToken:appleIdToken,
                                                                  rawNonce:rawNonce];
[[FIRAuth auth].currentUser
    reauthenticateWithCredential:credential
                      completion:^(FIRAuthDataResult * _Nullable authResult,
                                   NSError * _Nullable error) {
  if (error) {
    // Handle error.
  }
  // Apple user successfully re-authenticated.
  // ...
}];

Además, puedes usar linkWithCredential() para vincular diferentes proveedores de identidad a cuentas existentes.

Ten en cuenta que Apple requiere que obtengas el consentimiento explícito de los usuarios antes de vincular sus cuentas de Apple a otros datos.

Acceder con Apple no te permitirá reutilizar una credencial de autenticación para vincularla a una cuenta existente. Si deseas vincular una credencial de acceso con Apple a otra cuenta, primero debes intentar vincular las cuentas con la credencial de acceso con Apple anterior y, luego, examinar el error que se muestra para encontrar una nueva credencial. La credencial nueva se ubicará en el diccionario userInfo del error y se podrá acceder a ella mediante la clave AuthErrorUserInfoUpdatedCredentialKey.

Por ejemplo, para vincular una cuenta de Facebook a la cuenta actual de Firebase, usa el token de acceso que obtuviste cuando hiciste que el usuario accediera a Facebook:

Swift

// Initialize a Facebook credential with Firebase.
let credential = FacebookAuthProvider.credential(
  withAccessToken: AccessToken.current!.tokenString
)
// Assuming the current user is an Apple user linking a Facebook provider.
Auth.auth().currentUser.link(with: credential) { (authResult, error) in
  // Facebook credential is linked to the current Apple user.
  // The user can now sign in with Facebook or Apple to the same Firebase
  // account.
  // ...
}

Objective‑C

// Initialize a Facebook credential with Firebase.
FacebookAuthCredential *credential = [FIRFacebookAuthProvider credentialWithAccessToken:accessToken];
// Assuming the current user is an Apple user linking a Facebook provider.
[FIRAuth.auth linkWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
  // Facebook credential is linked to the current Apple user.
  // The user can now sign in with Facebook or Apple to the same Firebase
  // account.
  // ...
}];

Revocación de tokens

Apple exige que las apps que admiten la creación de cuentas permitan que los usuarios inicien la eliminación de su cuenta dentro de la app, como se describe en las Pautas de revisión de App Store.

Para cumplir con este requisito, implementa los siguientes pasos:

  1. Asegúrate de completar la sección ID de servicios y la configuración de flujo de código OAuth de la configuración del proveedor de acceso con Apple, como se describe en la sección Configura el inicio de sesión con Apple.

  2. Debido a que Firebase no almacena tokens de usuario cuando los usuarios se crean con la función de acceso con Apple, debes solicitar al usuario que vuelva a acceder antes de revocar su token y borrar la cuenta.

    Swift

    private func deleteCurrentUser() {
      do {
        let nonce = try CryptoUtils.randomNonceString()
        currentNonce = nonce
        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [.fullName, .email]
        request.nonce = CryptoUtils.sha256(nonce)
    
        let authorizationController = ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = self
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()
      } catch {
        // In the unlikely case that nonce generation fails, show error view.
        displayError(error)
      }
    }
    
    
  3. Obtén el código de autorización de ASAuthorizationAppleIDCredential y úsalo para llamar a Auth.auth().revokeToken(withAuthorizationCode:) para revocar los tokens del usuario.

    Swift

    func authorizationController(controller: ASAuthorizationController,
                                 didCompleteWithAuthorization authorization: ASAuthorization) {
      guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential
      else {
        print("Unable to retrieve AppleIDCredential")
        return
      }
    
      guard let _ = currentNonce else {
        fatalError("Invalid state: A login callback was received, but no login request was sent.")
      }
    
      guard let appleAuthCode = appleIDCredential.authorizationCode else {
        print("Unable to fetch authorization code")
        return
      }
    
      guard let authCodeString = String(data: appleAuthCode, encoding: .utf8) else {
        print("Unable to serialize auth code string from data: \(appleAuthCode.debugDescription)")
        return
      }
    
      Task {
        do {
          try await Auth.auth().revokeToken(withAuthorizationCode: authCodeString)
          try await user?.delete()
          self.updateUI()
        } catch {
          self.displayError(error)
        }
      }
    }
    
    
  4. Por último, borra la cuenta de usuario (y todos los datos asociados).

Próximos pasos

Cuando un usuario accede por primera vez, se crea una cuenta de usuario nueva y se la vincula con las credenciales (el nombre de usuario y la contraseña, el número de teléfono o la información del proveedor de autenticación) que el usuario utilizó para acceder. Esta cuenta nueva se almacena como parte de tu proyecto de Firebase y se puede usar para identificar a un usuario en todas las apps del proyecto, sin importar cómo acceda.

  • En tus apps, puedes obtener la información básica de perfil del usuario a partir del objeto User. Consulta cómo administrar usuarios.

  • En tus reglas de seguridad de Firebase Realtime Database y Cloud Storage, puedes obtener el ID del usuario único que accedió a partir de la variable auth y usarlo para controlar a qué datos podrá acceder.

Para permitir que los usuarios accedan a la app con varios proveedores de autenticación, puedes vincular las credenciales de estos proveedores con una cuenta de usuario existente.

Para salir de la sesión de un usuario, llama a signOut:.

Swift

let firebaseAuth = Auth.auth()
do {
  try firebaseAuth.signOut()
} catch let signOutError as NSError {
  print("Error signing out: %@", signOutError)
}

Objective‑C

NSError *signOutError;
BOOL status = [[FIRAuth auth] signOut:&signOutError];
if (!status) {
  NSLog(@"Error signing out: %@", signOutError);
  return;
}

Tal vez sea conveniente que agregues código de manejo de errores para todos los errores de autenticación. Consulta cómo solucionar errores.