Permite que los usuarios accedan con Facebook en iOS

Puedes permitir que tus usuarios se autentiquen con Identity Platform mediante sus cuentas de Facebook si integras el Acceso con Facebook o el Acceso limitado a Facebook en tu app. En esta página, se muestra cómo usar Identity Platform a fin de agregar el Acceso con Facebook a tu app para iOS.

Antes de comenzar

  1. Agrega Firebase a tu proyecto de iOS.

  2. Incluye los siguientes Pods en el Podfile:

    pod 'FirebaseAuth'
    
  3. Ve a la página Proveedores de identidad en la consola de Google Cloud.

    Ir a la página Proveedores de identidad

  4. Haz clic en Agregar un proveedor.

  5. Selecciona Facebook en la lista.

  6. Ingresa el ID de la aplicación de Facebook y el secreto de la aplicación. Si aún no tienes un ID y un secreto, puedes obtener uno en la página de Facebook para desarrolladores.

  7. Configura el URI que aparece en Configurar Facebook como un URI de redireccionamiento de OAuth válido para tu app de Facebook. Si configuraste un dominio personalizado en Identity Platform, actualiza el URI de redireccionamiento en la configuración de tu app de Facebook para usar el dominio personalizado en lugar del dominio predeterminado. Por ejemplo, cambia https://myproject.firebaseapp.com/__/auth/handler a https://auth.myownpersonaldomain.com/__/auth/handler.

  8. Para registrar los dominios de tu app, haz clic en Agregar dominio en Dominios autorizados. Para fines de desarrollo, localhost ya está habilitado de forma predeterminada.

  9. En Configura tu aplicación, haz clic en Detalles de la configuración. Copia el fragmento en el código de tu app para inicializar el SDK de cliente de Identity Platform.

  10. Haz clic en Guardar.

Implementa el Acceso con Facebook

Para usar el Acceso con Facebook “clásico”, completa los siguientes pasos. También puedes usar el Acceso limitado a Facebook, como se muestra en la siguiente sección.

  1. Para integrar el servicio de Acceso con Facebook en la app, sigue las indicaciones que se brindan en la documentación para desarrolladores. Cuando inicialices el objeto FBSDKLoginButton, configura un delegado que reciba los eventos de acceso y salida. Por ejemplo:

    Swift

    let loginButton = FBSDKLoginButton()
    loginButton.delegate = self
    

    Objective-C

    FBSDKLoginButton *loginButton = [[FBSDKLoginButton alloc] init];
    loginButton.delegate = self;
    
    En tu delegado, implementa didCompleteWithResult:error:.

    Swift

    func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
      if let error = error {
        print(error.localizedDescription)
        return
      }
      // ...
    }
    

    Objective-C

    - (void)loginButton:(FBSDKLoginButton *)loginButton
        didCompleteWithResult:(FBSDKLoginManagerLoginResult *)result
                        error:(NSError *)error {
      if (error == nil) {
        // ...
      } else {
        NSLog(error.localizedDescription);
      }
    }
    
  2. Importa el módulo de Firebase en UIApplicationDelegate:

    Swift

    import FirebaseCore
    import FirebaseAuth
          

    Objective-C

    @import FirebaseCore;
    @import FirebaseAuth;
          
  3. Configura una instancia compartida de FirebaseApp, generalmente en el método application:didFinishLaunchingWithOptions: de tu app:

    Swift

    // Use Firebase library to configure APIs
    FirebaseApp.configure()

    Objective-C

    // Use Firebase library to configure APIs
    [FIRApp configure];
  4. Una vez que un usuario acceda correctamente, en la implementación de didCompleteWithResult:error:, obtén un token de acceso para el usuario que accedió y cámbialo por una credencial de Identity Platform:

    Swift

    let credential = FacebookAuthProvider
      .credential(withAccessToken: AccessToken.current!.tokenString)
    

    Objective-C

    FIRAuthCredential *credential = [FIRFacebookAuthProvider
        credentialWithAccessToken:[FBSDKAccessToken currentAccessToken].tokenString];
    

Implementa el Acceso limitado a Facebook

Para usar el Acceso restringido a Facebook en lugar de la versión “clásica” del Acceso con Facebook, completa los siguientes pasos.

  1. Para integrar el servicio de Acceso limitado a Facebook en la app, sigue las indicaciones que se brindan en la documentación para desarrolladores.
  2. Para cada solicitud de acceso, genera una string aleatoria (un “nonce”) que utilizarás a fin de asegurarte de que el token de ID que obtuviste se otorgó 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 en iOS 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];
    }
            
    Envías el hash SHA-256 del nonce con tu solicitud de acceso, que Facebook pasará sin cambios en la respuesta. Identity Platform valida la respuesta mediante la generación de un hash del nonce original y su comparación con el valor que pasa Facebook.

    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;
    }
            
  3. Cuando configures FBSDKLoginButton, configura un delegado para recibir eventos de acceso y cierre de sesión, establece el modo de seguimiento en FBSDKLoginTrackingLimited y adjunta un nonce. Por ejemplo:

    Swift

    func setupLoginButton() {
        let nonce = randomNonceString()
        currentNonce = nonce
        loginButton.delegate = self
        loginButton.loginTracking = .limited
        loginButton.nonce = sha256(nonce)
    }
            

    Objective-C

    - (void)setupLoginButton {
      NSString *nonce = [self randomNonce:32];
      self.currentNonce = nonce;
      self.loginButton.delegate = self;
      self.loginButton.loginTracking = FBSDKLoginTrackingLimited
      self.loginButton.nonce = [self stringBySha256HashingString:nonce];
    }
            
    En tu delegado, implementa didCompleteWithResult:error:.

    Swift

    func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
      if let error = error {
        print(error.localizedDescription)
        return
      }
      // ...
    }
            

    Objective-C

    - (void)loginButton:(FBSDKLoginButton *)loginButton
        didCompleteWithResult:(FBSDKLoginManagerLoginResult *)result
                        error:(NSError *)error {
      if (error == nil) {
        // ...
      } else {
        NSLog(error.localizedDescription);
      }
    }
            
  4. Importa el módulo de Firebase en UIApplicationDelegate:

    Swift

    import Firebase

    Objective-C

    @import Firebase;
  5. Configura una instancia compartida de FirebaseApp, generalmente en el método application:didFinishLaunchingWithOptions: de tu app:

    Swift

    // Use Firebase library to configure APIs
    FirebaseApp.configure()

    Objective-C

    // Use Firebase library to configure APIs
    [FIRApp configure];
  6. Después de que un usuario acceda de forma correcta, en tu implementación de didCompleteWithResult:error:, usa el token de ID de la respuesta de Facebook con el nonce sin hash para obtener una credencial de Identity Platform:

    Swift

    // Initialize an Identity Platform credential.
    let idTokenString = AuthenticationToken.current?.tokenString
    let nonce = currentNonce
    let credential = OAuthProvider.credential(withProviderID: "facebook.com",
                                              IDToken: idTokenString,
                                              rawNonce: nonce)
            

    Objective-C

    // Initialize an Identity Platform credential.
    NSString *idTokenString = FBSDKAuthenticationToken.currentAuthenticationToken.tokenString;
    NSString *rawNonce = self.currentNonce;
    FIROAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"facebook.com"
                                                                        IDToken:idTokenString
                                                                       rawNonce:rawNonce];
            

Autentica con Identity Platform

Por último, autentíca con Identity Platform mediante la credencial de Identity Platform:

Swift

Auth.auth().signIn(with: credential) { authResult, error in
    if let error = error {
      let authError = error as NSError
      if isMFAEnabled, authError.code == AuthErrorCode.secondFactorRequired.rawValue {
        // The user is a multi-factor user. Second factor challenge is required.
        let resolver = authError
          .userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver
        var displayNameString = ""
        for tmpFactorInfo in resolver.hints {
          displayNameString += tmpFactorInfo.displayName ?? ""
          displayNameString += " "
        }
        self.showTextInputPrompt(
          withMessage: "Select factor to sign in\n\(displayNameString)",
          completionBlock: { userPressedOK, displayName in
            var selectedHint: PhoneMultiFactorInfo?
            for tmpFactorInfo in resolver.hints {
              if displayName == tmpFactorInfo.displayName {
                selectedHint = tmpFactorInfo as? PhoneMultiFactorInfo
              }
            }
            PhoneAuthProvider.provider()
              .verifyPhoneNumber(with: selectedHint!, uiDelegate: nil,
                                 multiFactorSession: resolver
                                   .session) { verificationID, error in
                if error != nil {
                  print(
                    "Multi factor start sign in failed. Error: \(error.debugDescription)"
                  )
                } else {
                  self.showTextInputPrompt(
                    withMessage: "Verification code for \(selectedHint?.displayName ?? "")",
                    completionBlock: { userPressedOK, verificationCode in
                      let credential: PhoneAuthCredential? = PhoneAuthProvider.provider()
                        .credential(withVerificationID: verificationID!,
                                    verificationCode: verificationCode!)
                      let assertion: MultiFactorAssertion? = PhoneMultiFactorGenerator
                        .assertion(with: credential!)
                      resolver.resolveSignIn(with: assertion!) { authResult, error in
                        if error != nil {
                          print(
                            "Multi factor finanlize sign in failed. Error: \(error.debugDescription)"
                          )
                        } else {
                          self.navigationController?.popViewController(animated: true)
                        }
                      }
                    }
                  )
                }
              }
          }
        )
      } else {
        self.showMessagePrompt(error.localizedDescription)
        return
      }
      // ...
      return
    }
    // User is signed in
    // ...
}
    

Objective-C

[[FIRAuth auth] signInWithCredential:credential
                          completion:^(FIRAuthDataResult * _Nullable authResult,
                                       NSError * _Nullable error) {
    if (isMFAEnabled && error && error.code == FIRAuthErrorCodeSecondFactorRequired) {
      FIRMultiFactorResolver *resolver = error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];
      NSMutableString *displayNameString = [NSMutableString string];
      for (FIRMultiFactorInfo *tmpFactorInfo in resolver.hints) {
        [displayNameString appendString:tmpFactorInfo.displayName];
        [displayNameString appendString:@" "];
      }
      [self showTextInputPromptWithMessage:[NSString stringWithFormat:@"Select factor to sign in\n%@", displayNameString]
                           completionBlock:^(BOOL userPressedOK, NSString *_Nullable displayName) {
       FIRPhoneMultiFactorInfo* selectedHint;
       for (FIRMultiFactorInfo *tmpFactorInfo in resolver.hints) {
         if ([displayName isEqualToString:tmpFactorInfo.displayName]) {
           selectedHint = (FIRPhoneMultiFactorInfo *)tmpFactorInfo;
         }
       }
       [FIRPhoneAuthProvider.provider
        verifyPhoneNumberWithMultiFactorInfo:selectedHint
        UIDelegate:nil
        multiFactorSession:resolver.session
        completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
          if (error) {
            [self showMessagePrompt:error.localizedDescription];
          } else {
            [self showTextInputPromptWithMessage:[NSString stringWithFormat:@"Verification code for %@", selectedHint.displayName]
                                 completionBlock:^(BOOL userPressedOK, NSString *_Nullable verificationCode) {
             FIRPhoneAuthCredential *credential =
                 [[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID
                                                              verificationCode:verificationCode];
             FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
             [resolver resolveSignInWithAssertion:assertion completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
               if (error) {
                 [self showMessagePrompt:error.localizedDescription];
               } else {
                 NSLog(@"Multi factor finanlize sign in succeeded.");
               }
             }];
           }];
          }
        }];
     }];
    }
  else if (error) {
    // ...
    return;
  }
  // User successfully signed in. Get user data from the FIRUser object
  if (authResult == nil) { return; }
  FIRUser *user = authResult.user;
  // ...
}];
    

¿Qué sigue?