Autenticar usando Apple

Puede permitir que sus usuarios se autentiquen con Firebase utilizando su ID de Apple mediante el SDK de Firebase para llevar a cabo el flujo de inicio de sesión de OAuth 2.0 de un extremo a otro.

Antes de que empieces

Para iniciar sesión como usuario con Apple, primero configure Iniciar sesión con Apple en el sitio para desarrolladores de Apple y luego habilite Apple como proveedor de inicio de sesión para su proyecto de Firebase.

Únase al programa de desarrolladores de Apple

Iniciar sesión con Apple solo lo pueden configurar los miembros del Programa de desarrolladores de Apple .

Configurar Iniciar sesión con Apple

  1. Habilite Iniciar sesión con Apple para su aplicación en la página Certificados, identificadores y perfiles del sitio para desarrolladores de Apple.
  2. Asocie su sitio web con su aplicación como se describe en la primera sección de Configurar el inicio de sesión con Apple para la web . Cuando se le solicite, registre la siguiente URL como URL de retorno:
    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
    Puede obtener el ID de su proyecto de Firebase en la página de configuración de la consola de Firebase . Cuando haya terminado, tome nota de su nuevo ID de servicio, que necesitará en la siguiente sección.
  3. Cree un inicio de sesión con la clave privada de Apple . Necesitará su nueva clave privada y su ID de clave en la siguiente sección.
  4. Si utiliza cualquiera de las funciones de Firebase Authentication que envían correos electrónicos a los usuarios, incluido el inicio de sesión mediante enlace de correo electrónico, la verificación de la dirección de correo electrónico, la revocación de cambio de cuenta y otras, configure el servicio de retransmisión de correo electrónico privado de Apple y regístrese noreply@ YOUR_FIREBASE_PROJECT_ID .firebaseapp.com (o su dominio de plantilla de correo electrónico personalizado) para que Apple pueda transmitir los correos electrónicos enviados mediante Firebase Authentication a direcciones de correo electrónico anónimas de Apple.

Habilite Apple como proveedor de inicio de sesión

  1. Agrega Firebase a tu proyecto de Apple . Asegúrate de registrar el ID del paquete de tu aplicación cuando la configures en Firebase console.
  2. En Firebase console , abre la sección Auth . En la pestaña Método de inicio de sesión , habilite el proveedor de Apple . Especifique el ID de servicio que creó en la sección anterior. Además, en la sección de configuración del flujo de código OAuth , especifique su ID de equipo Apple y la clave privada y el ID de clave que creó en la sección anterior.

Cumplir con los requisitos de datos anonimizados de Apple

Iniciar sesión con Apple ofrece a los usuarios la opción de anonimizar sus datos, incluida su dirección de correo electrónico, al iniciar sesión. Los usuarios que eligen esta opción tienen direcciones de correo electrónico con el dominio privaterelay.appleid.com . Cuando utiliza Iniciar sesión con Apple en su aplicación, debe cumplir con las políticas o términos de desarrollador aplicables de Apple con respecto a estos ID de Apple anónimos.

Esto incluye obtener el consentimiento del usuario requerido antes de asociar cualquier información personal de identificación directa con una ID de Apple anónima. Cuando se utiliza la autenticación de Firebase, esto puede incluir las siguientes acciones:

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

La lista de arriba no es exhaustiva. Consulte el Acuerdo de licencia del programa para desarrolladores de Apple en la sección Membresía de su cuenta de desarrollador para asegurarse de que su aplicación cumpla con los requisitos de Apple.

Inicia sesión con Apple y autentícate con Firebase

Para autenticarse con una cuenta de Apple, primero inicie sesión como usuario en su cuenta de Apple usando el marco AuthenticationServices de Apple y luego use el token de ID de la respuesta de Apple para crear un objeto AuthCredential Firebase:

  1. Para cada solicitud de inicio de sesión, genere una cadena aleatoria (un "nonce") que usará para asegurarse de que el token de identificación que obtenga se haya otorgado específicamente en respuesta a la solicitud de autenticación de su aplicación. Este paso es importante para evitar ataques de repetición.

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

    Rápido

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

    C objetivo

    // 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á el hash SHA256 del nonce con su solicitud de inicio de sesión, que Apple pasará sin cambios en la respuesta. Firebase valida la respuesta aplicando hash al nonce original y comparándolo con el valor pasado por Apple.

    Rápido

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

    C objetivo

    - (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. Inicie el flujo de inicio de sesión de Apple, incluyendo en su solicitud el hash SHA256 del nonce y la clase delegada que manejará la respuesta de Apple (consulte el siguiente paso):

    Rápido

    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()
    }
    

    C objetivo

    @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. Maneje la respuesta de Apple en su implementación de ASAuthorizationControllerDelegate . Si el inicio de sesión fue exitoso, use el token de identificación de la respuesta de Apple con el nonce sin hash para autenticarse con Firebase:

    Rápido

    @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)")
      }
    
    }
    

    C objetivo

    - (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 aplicación, Apple proporciona una dirección de correo electrónico única para ese usuario (del formato xyz@privaterelay.appleid.com ), que comparte con su aplicación. Si configuró el servicio de retransmisión de correo electrónico privado, Apple reenvía los correos electrónicos enviados a la dirección anónima a la dirección de correo electrónico real del usuario.

Reautenticación y vinculación de cuentas

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

Rápido

// 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.
  // ...
}

C objetivo

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.
  // ...
}];

Y puede utilizar linkWithCredential() para vincular diferentes proveedores de identidad a cuentas existentes.

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

Iniciar sesión con Apple no le permitirá reutilizar una credencial de autenticación para vincularla a una cuenta existente. Si desea vincular una credencial de Iniciar sesión con Apple a otra cuenta, primero debe intentar vincular las cuentas utilizando la credencial de Iniciar sesión con Apple anterior y luego examinar el error que aparece para encontrar una nueva credencial. La nueva credencial 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, use el token de acceso que obtuvo al iniciar sesión en Facebook:

Rápido

// 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.
  // ...
}

C objetivo

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

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

Para cumplir con este requisito, implemente los siguientes pasos:

  1. Asegúrese de completar la sección de configuración de flujo de código OAuth y ID de servicios de la configuración del proveedor Iniciar sesión con Apple, como se describe en la sección Configurar Iniciar sesión con Apple .

  2. Dado que Firebase no almacena tokens de usuario cuando los usuarios se crean con Iniciar sesión con Apple, debes pedirle al usuario que inicie sesión nuevamente antes de revocar su token y eliminar la cuenta.

    Rápido

    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. Obtenga el código de autorización de ASAuthorizationAppleIDCredential y utilícelo para llamar Auth.auth().revokeToken(withAuthorizationCode:) para revocar los tokens del usuario.

    Rápido

    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. Finalmente, elimine la cuenta de usuario (y todos los datos asociados)

Próximos pasos

Después de que un usuario inicia sesión por primera vez, se crea una nueva cuenta de usuario y se vincula a las credenciales (es decir, el nombre de usuario y la contraseña, el número de teléfono o la información del proveedor de autenticación) con las que el usuario inició sesión. Esta nueva cuenta se almacena como parte de su proyecto de Firebase y se puede usar para identificar a un usuario en cada aplicación de su proyecto, independientemente de cómo inicie sesión el usuario.

  • En sus aplicaciones, puede obtener la información básica del perfil del usuario desde el objeto User . Consulte Administrar usuarios .

  • En las reglas de seguridad de Firebase Realtime Database y Cloud Storage, puede obtener el ID de usuario único del usuario que inició sesión a partir de la variable auth y usarlo para controlar a qué datos puede acceder un usuario.

Puede permitir que los usuarios inicien sesión en su aplicación utilizando múltiples proveedores de autenticación vinculando las credenciales del proveedor de autenticación a una cuenta de usuario existente.

Para cerrar la sesión de un usuario, llame signOut: .

Rápido

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

C objetivo

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

Es posible que también desee agregar un código de manejo de errores para toda la gama de errores de autenticación. Consulte Manejar errores .