Agregue autenticación multifactor a su aplicación iOS

Si actualizó a Firebase Authentication con Identity Platform, puede agregar autenticación multifactor por SMS a su aplicación iOS.

La autenticación multifactor aumenta la seguridad de su aplicación. Si bien los atacantes suelen comprometer contraseñas y cuentas sociales, interceptar un mensaje de texto es más difícil.

Antes de que empieces

  1. Habilite al menos un proveedor que admita la autenticación multifactor. Todos los proveedores admiten MFA, excepto la autenticación telefónica, la autenticación anónima y Apple Game Center.

  2. Asegúrese de que su aplicación verifique los correos electrónicos de los usuarios. MFA requiere verificación por correo electrónico. Esto evita que actores maliciosos se registren en un servicio con un correo electrónico que no les pertenece y luego bloqueen al propietario real agregando un segundo factor.

Habilitar la autenticación multifactor

  1. Abra la página Autenticación > Método de inicio de sesión de Firebase console.

  2. En la sección Avanzado , habilite la autenticación multifactor por SMS .

    También debes ingresar los números de teléfono con los que probarás tu aplicación. Si bien es opcional, se recomienda encarecidamente registrar números de teléfono de prueba para evitar limitaciones durante el desarrollo.

  3. Si aún no ha autorizado el dominio de su aplicación, agréguelo a la lista de permitidos en la página Autenticación > Configuración de Firebase console.

Verificando tu aplicación

Firebase necesita verificar que las solicitudes de SMS provengan de su aplicación. Puede hacer esto de dos maneras:

  • Notificaciones silenciosas de APN : cuando inicia sesión con un usuario por primera vez, Firebase puede enviar una notificación push silenciosa al dispositivo del usuario. La autenticación puede continuar si la aplicación recibe la notificación. Tenga en cuenta que a partir de iOS 8.0, no es necesario pedirle al usuario que permita las notificaciones automáticas para utilizar este método.

  • Verificación de reCAPTCHA : si no puede enviar una notificación silenciosa (por ejemplo, porque el usuario ha deshabilitado la actualización en segundo plano o está probando su aplicación en el simulador de iOS), puede usar reCAPTCHA. En muchos casos, reCAPTCHA se resolverá solo automáticamente sin interacción del usuario.

Usar notificaciones silenciosas

Para habilitar las notificaciones de APN para usar con Firebase:

  1. En Xcode, habilite las notificaciones push para su proyecto.

  2. Cargue su clave de autenticación de APN usando Firebase Console (sus cambios se transferirán automáticamente a Google Cloud Firebase). Si aún no tiene su clave de autenticación de APN, consulte Configuración de APN con FCM para saber cómo obtenerla.

    1. Abra la consola de Firebase .

    2. Vaya a Configuración del proyecto .

    3. Seleccione la pestaña Mensajería en la nube .

    4. En Clave de autenticación de APN , en la sección de configuración de la aplicación iOS , haga clic en Cargar .

    5. Seleccione su clave.

    6. Agregue el ID de clave para la clave. Puede encontrar el ID de clave en Certificados, identificadores y perfiles en el Centro de miembros desarrolladores de Apple .

    7. Haga clic en Cargar .

Si ya tiene un certificado APN, puede cargar el certificado en su lugar.

Usando la verificación reCAPTCHA

Para permitir que el SDK del cliente utilice reCAPTCHA:

  1. Abra la configuración de su proyecto en Xcode.

  2. Haga doble clic en el nombre del proyecto en la vista de árbol de la izquierda.

  3. Seleccione su aplicación en la sección Objetivos .

  4. Seleccione la pestaña Información .

  5. Expanda la sección Tipos de URL .

  6. Haga clic en el botón + .

  7. Ingrese su ID de cliente invertido en el campo Esquemas de URL . Puede encontrar este valor en el archivo de configuración GoogleService-Info.plist como REVERSED_CLIENT_ID .

Cuando esté completa, su configuración debería verse similar a la siguiente:

Esquemas personalizados

Opcionalmente, puede personalizar la forma en que su aplicación presenta SFSafariViewController o UIWebView al mostrar reCAPTCHA. Para hacer esto, cree una clase personalizada que se ajuste al protocolo FIRAuthUIDelegate y pásela a verifyPhoneNumber:UIDelegate:completion: .

Elegir un patrón de inscripción

Puede elegir si su aplicación requiere autenticación multifactor y cómo y cuándo inscribir a sus usuarios. Algunos patrones comunes incluyen:

  • Inscriba el segundo factor del usuario como parte del registro. Utilice este método si su aplicación requiere autenticación multifactor para todos los usuarios. Tenga en cuenta que una cuenta debe tener una dirección de correo electrónico verificada para inscribir un segundo factor, por lo que su flujo de registro deberá adaptarse a esto.

  • Ofrezca una opción que se pueda omitir para inscribir un segundo factor durante el registro. Las aplicaciones que quieran fomentar, pero no exigir, la autenticación multifactor pueden preferir este enfoque.

  • Brinde la posibilidad de agregar un segundo factor desde la cuenta del usuario o la página de administración de perfil, en lugar de la pantalla de registro. Esto minimiza la fricción durante el proceso de registro y al mismo tiempo hace que la autenticación multifactor esté disponible para los usuarios sensibles a la seguridad.

  • Requerir agregar un segundo factor de manera incremental cuando el usuario desee acceder a funciones con mayores requisitos de seguridad.

Inscribir un segundo factor

Para inscribir un nuevo factor secundario para un usuario:

  1. Vuelva a autenticar al usuario.

  2. Solicite al usuario que ingrese su número de teléfono.

  3. Obtenga una sesión multifactor para el usuario:

    Rápido

    authResult.user.multiFactor.getSessionWithCompletion() { (session, error) in
      // ...
    }
    

    C objetivo

    [authResult.user.multiFactor
      getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session,
                                NSError * _Nullable error) {
        // ...
    }];
    
  4. Enviar un mensaje de verificación al teléfono del usuario. Asegúrese de que el número de teléfono esté formateado con un + inicial y sin otra puntuación o espacio en blanco (por ejemplo: +15105551234 ).

    Rápido

    // Send SMS verification code.
    PhoneAuthProvider.provider().verifyPhoneNumber(
      phoneNumber,
      uiDelegate: nil,
      multiFactorSession: session) { (verificationId, error) in
        // verificationId will be needed for enrollment completion.
    }
    

    C objetivo

    // Send SMS verification code.
    [FIRPhoneAuthProvider.provider verifyPhoneNumber:phoneNumber
                                          UIDelegate:nil
                                  multiFactorSession:session
                                          completion:^(NSString * _Nullable verificationID,
                                                        NSError * _Nullable error) {
        // verificationId will be needed for enrollment completion.
    }];
    

    Si bien no es obligatorio, es una buena práctica informar a los usuarios de antemano que recibirán un mensaje SMS y que se aplican tarifas estándar.

    El método verifyPhoneNumber() inicia el proceso de verificación de la aplicación en segundo plano mediante una notificación push silenciosa. Si la notificación push silenciosa no está disponible, en su lugar se emite un desafío reCAPTCHA.

  5. Una vez enviado el código SMS, solicite al usuario que verifique el código. Luego, use su respuesta para crear una PhoneAuthCredential :

    Rápido

    // Ask user for the verification code. Then:
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId,
      verificationCode: verificationCode)
    

    C objetivo

    // Ask user for the SMS verification code. Then:
    FIRPhoneAuthCredential *credential = [FIRPhoneAuthProvider.provider
                                           credentialWithVerificationID:verificationID
                                           verificationCode:kPhoneSecondFactorVerificationCode];
    
  6. Inicializar un objeto de aserción:

    Rápido

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    C objetivo

    FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  7. Completa la inscripción. Opcionalmente, puede especificar un nombre para mostrar para el segundo factor. Esto es útil para usuarios con múltiples segundos factores, ya que el número de teléfono está enmascarado durante el flujo de autenticación (por ejemplo, +1******1234).

    Rápido

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    user.multiFactor.enroll(with: assertion, displayName: displayName) { (error) in
      // ...
    }
    

    C objetivo

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    [authResult.user.multiFactor enrollWithAssertion:assertion
                                         displayName:nil
                                          completion:^(NSError * _Nullable error) {
        // ...
    }];
    

El siguiente código muestra un ejemplo completo de cómo registrar un segundo factor:

Rápido

let user = Auth.auth().currentUser
user?.multiFactor.getSessionWithCompletion({ (session, error) in
  // Send SMS verification code.
  PhoneAuthProvider.provider().verifyPhoneNumber(
    phoneNumber,
    uiDelegate: nil,
    multiFactorSession: session
  ) { (verificationId, error) in
    // verificationId will be needed for enrollment completion.
    // Ask user for the verification code.
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId!,
      verificationCode: phoneSecondFactorVerificationCode)
    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    user?.multiFactor.enroll(with: assertion, displayName: displayName) { (error) in
      // ...
    }
  }
})

C objetivo

FIRUser *user = FIRAuth.auth.currentUser;
[user.multiFactor getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session,
                                              NSError * _Nullable error) {
    // Send SMS verification code.
    [FIRPhoneAuthProvider.provider
      verifyPhoneNumber:phoneNumber
      UIDelegate:nil
      multiFactorSession:session
      completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
        // verificationId will be needed for enrollment completion.

        // Ask user for the verification code.
        // ...

        // Then:
        FIRPhoneAuthCredential *credential =
            [FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationID
                                                        verificationCode:kPhoneSecondFactorVerificationCode];
        FIRMultiFactorAssertion *assertion =
            [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];

        // Complete enrollment. This will update the underlying tokens
        // and trigger ID token change listener.
        [user.multiFactor enrollWithAssertion:assertion
                                  displayName:displayName
                                    completion:^(NSError * _Nullable error) {
            // ...
        }];
    }];
}];

¡Felicidades! Registró exitosamente un segundo factor de autenticación para un usuario.

Iniciar sesión de usuarios con un segundo factor

Para iniciar sesión como usuario con verificación por SMS de dos factores:

  1. Inicie sesión como usuario con su primer factor y luego detecte un error que indica que se requiere autenticación multifactor. Este error contiene un solucionador, sugerencias sobre los segundos factores inscritos y una sesión subyacente que demuestra que el usuario se autenticó exitosamente con el primer factor.

    Por ejemplo, si el primer factor del usuario fue un correo electrónico y una contraseña:

    Rápido

    Auth.auth().signIn(
      withEmail: email,
      password: password
    ) { (result, error) in
      let authError = error as NSError
      if authError?.code == AuthErrorCode.secondFactorRequired.rawValue {
        // The user is a multi-factor user. Second factor challenge is required.
        let resolver =
          authError!.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver
        // ...
      } else {
        // Handle other errors such as wrong password.
      }
    }
    

    C objetivo

    [FIRAuth.auth signInWithEmail:email
                         password:password
                       completion:^(FIRAuthDataResult * _Nullable authResult,
                                    NSError * _Nullable error) {
        if (error == nil || error.code != FIRAuthErrorCodeSecondFactorRequired) {
            // User is not enrolled with a second factor and is successfully signed in.
            // ...
        } else {
            // The user is a multi-factor user. Second factor challenge is required.
        }
    }];
    

    Si el primer factor del usuario es un proveedor federado, como OAuth, detecte el error después de llamar getCredentialWith() .

  2. Si el usuario tiene varios factores secundarios inscritos, pregúntele cuál usar. Puede obtener el número de teléfono enmascarado con resolver.hints[selectedIndex].phoneNumber y el nombre para mostrar con resolver.hints[selectedIndex].displayName .

    Rápido

    // Ask user which second factor to use. Then:
    if resolver.hints[selectedIndex].factorID == PhoneMultiFactorID {
      // User selected a phone second factor.
      // ...
    } else if resolver.hints[selectedIndex].factorID == TotpMultiFactorID {
      // User selected a TOTP second factor.
      // ...
    } else {
      // Unsupported second factor.
    }
    

    C objetivo

    FIRMultiFactorResolver *resolver =
        (FIRMultiFactorResolver *) error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];
    
    // Ask user which second factor to use. Then:
    FIRPhoneMultiFactorInfo *hint = (FIRPhoneMultiFactorInfo *) resolver.hints[selectedIndex];
    if (hint.factorID == FIRPhoneMultiFactorID) {
      // User selected a phone second factor.
      // ...
    } else if (hint.factorID == FIRTOTPMultiFactorID) {
      // User selected a TOTP second factor.
      // ...
    } else {
      // Unsupported second factor.
    }
    
  3. Enviar un mensaje de verificación al teléfono del usuario:

    Rápido

    // Send SMS verification code.
    let hint = resolver.hints[selectedIndex] as! PhoneMultiFactorInfo
    PhoneAuthProvider.provider().verifyPhoneNumber(
      with: hint,
      uiDelegate: nil,
      multiFactorSession: resolver.session
    ) { (verificationId, error) in
      // verificationId will be needed for sign-in completion.
    }
    

    C objetivo

    // Send SMS verification code
    [FIRPhoneAuthProvider.provider
      verifyPhoneNumberWithMultiFactorInfo:hint
      UIDelegate:nil
      multiFactorSession:resolver.session
      completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
        if (error != nil) {
            // Failed to verify phone number.
        }
    }];
    
  4. Una vez enviado el código SMS, pídale al usuario que verifique el código y utilícelo para crear una PhoneAuthCredential :

    Rápido

    // Ask user for the verification code. Then:
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId!,
      verificationCode: verificationCodeFromUser)
    

    C objetivo

    // Ask user for the SMS verification code. Then:
    FIRPhoneAuthCredential *credential =
        [FIRPhoneAuthProvider.provider
          credentialWithVerificationID:verificationID
                      verificationCode:verificationCodeFromUser];
    
  5. Inicialice un objeto de aserción con la credencial:

    Rápido

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    C objetivo

    FIRMultiFactorAssertion *assertion =
        [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  6. Resuelva el inicio de sesión. Luego podrá acceder al resultado del inicio de sesión original, que incluye los datos estándar específicos del proveedor y las credenciales de autenticación:

    Rápido

    // Complete sign-in. This will also trigger the Auth state listeners.
    resolver.resolveSignIn(with: assertion) { (authResult, error) in
      // authResult will also contain the user, additionalUserInfo, optional
      // credential (null for email/password) associated with the first factor sign-in.
    
      // For example, if the user signed in with Google as a first factor,
      // authResult.additionalUserInfo will contain data related to Google provider that
      // the user signed in with.
    
      // user.credential contains the Google OAuth credential.
      // user.credential.accessToken contains the Google OAuth access token.
      // user.credential.idToken contains the Google OAuth ID token.
    }
    

    C objetivo

    // Complete sign-in.
    [resolver resolveSignInWithAssertion:assertion
                              completion:^(FIRAuthDataResult * _Nullable authResult,
                                            NSError * _Nullable error) {
        if (error != nil) {
            // User successfully signed in with the second factor phone number.
        }
    }];
    

El siguiente código muestra un ejemplo completo de cómo iniciar sesión como usuario multifactor:

Rápido

Auth.auth().signIn(
  withEmail: email,
  password: password
) { (result, error) in
  let authError = error as NSError?
  if authError?.code == AuthErrorCode.secondFactorRequired.rawValue {
    let resolver =
      authError!.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver

    // Ask user which second factor to use.
    // ...

    // Then:
    let hint = resolver.hints[selectedIndex] as! PhoneMultiFactorInfo

    // Send SMS verification code
    PhoneAuthProvider.provider().verifyPhoneNumber(
      with: hint,
      uiDelegate: nil,
      multiFactorSession: resolver.session
    ) { (verificationId, error) in
      if error != nil {
        // Failed to verify phone number.
      }
      // Ask user for the SMS verification code.
      // ...

      // Then:
      let credential = PhoneAuthProvider.provider().credential(
        withVerificationID: verificationId!,
        verificationCode: verificationCodeFromUser)
      let assertion = PhoneMultiFactorGenerator.assertion(with: credential)

      // Complete sign-in.
      resolver.resolveSignIn(with: assertion) { (authResult, error) in
        if error != nil {
          // User successfully signed in with the second factor phone number.
        }
      }
    }
  }
}

C objetivo

[FIRAuth.auth signInWithEmail:email
                     password:password
                   completion:^(FIRAuthDataResult * _Nullable authResult,
                               NSError * _Nullable error) {
    if (error == nil || error.code != FIRAuthErrorCodeSecondFactorRequired) {
        // User is not enrolled with a second factor and is successfully signed in.
        // ...
    } else {
        FIRMultiFactorResolver *resolver =
            (FIRMultiFactorResolver *) error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];

        // Ask user which second factor to use.
        // ...

        // Then:
        FIRPhoneMultiFactorInfo *hint = (FIRPhoneMultiFactorInfo *) resolver.hints[selectedIndex];

        // Send SMS verification code
        [FIRPhoneAuthProvider.provider
          verifyPhoneNumberWithMultiFactorInfo:hint
                                    UIDelegate:nil
                            multiFactorSession:resolver.session
                                    completion:^(NSString * _Nullable verificationID,
                                                NSError * _Nullable error) {
            if (error != nil) {
                // Failed to verify phone number.
            }

            // Ask user for the SMS verification code.
            // ...

            // Then:
            FIRPhoneAuthCredential *credential =
                [FIRPhoneAuthProvider.provider
                  credentialWithVerificationID:verificationID
                              verificationCode:kPhoneSecondFactorVerificationCode];
            FIRMultiFactorAssertion *assertion =
                [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];

            // Complete sign-in.
            [resolver resolveSignInWithAssertion:assertion
                                      completion:^(FIRAuthDataResult * _Nullable authResult,
                                                    NSError * _Nullable error) {
                if (error != nil) {
                    // User successfully signed in with the second factor phone number.
                }
            }];
        }];
    }
}];

¡Felicidades! Ingresó exitosamente a un usuario mediante la autenticación multifactor.

Que sigue