Agregar autenticación de varios factores a una app para iOS

En este documento, se muestra cómo agregar la autenticación de varios factores mediante SMS a tu app para iOS.

La autenticación de varios factores aumenta la seguridad de tu app. Si bien los atacantes a menudo hackean las contraseñas y las cuentas de redes sociales, interceptar un mensaje de texto es más difícil.

Antes de comenzar

  1. Habilita al menos un proveedor que admita la autenticación de varios factores. Todos los proveedores admiten la MFA, excepto la autenticación por teléfono, la autenticación anónima y Game Center de Apple.

  2. Asegúrate de que en tu app se verifiquen los correos electrónicos de los usuarios. La MFA requiere la verificación por correo electrónico. Esto evita que los actores maliciosos se registren en un servicio con un correo electrónico que no les pertenece y, luego, agreguen un segundo factor para bloquear al propietario real.

Habilita la autenticación de varios factores

  1. Ve a la página MFA de Identity Platform en la consola de Google Cloud.
    Ir a la página MFA

  2. En el cuadro llamado Autenticación de varios factores basada en SMS, haz clic en Habilitar.

  3. Ingresa los números de teléfono con los que probarás tu app. Si bien es opcional, se recomienda registrar los números de teléfono de prueba para evitar los límites durante el desarrollo.

  4. Si aún no autorizaste el dominio de tu app, haz clic en Agregar dominio a la derecha para agregarlo a la lista de permisos.

  5. Haz clic en Guardar.

Verifica tu app

Identity Platform necesita verificar que las solicitudes por SMS provengan de tu app. Puedes hacerlo de dos maneras:

  • Notificaciones silenciosas de APNS: Cuando permites que un usuario acceda por primera vez, Identity Platform puede enviar una notificación push silenciosa al dispositivo del usuario. La autenticación puede continuar si la aplicación recibe la notificación. Ten en cuenta que, a partir de iOS 8.0, no necesitas pedirle al usuario que permita las notificaciones push para usar este método.

  • Verificación de reCAPTCHA: Si no puedes enviar una notificación silenciosa (por ejemplo, porque el usuario inhabilitó la actualización en segundo plano o pruebas la app en el simulador de iOS), puedes usar reCAPTCHA. En muchos casos, el reCAPTCHA se resolverá automáticamente sin interacción de parte del usuario.

Usa notificaciones silenciosas

Para habilitar las notificaciones de APNS con el fin de usarlas con Identity Platform, sigue estos pasos:

  1. En Xcode, habilita las notificaciones push para el proyecto.

  2. Sube la clave de autenticación de APNS mediante Firebase Console (los cambios se transferirán automáticamente a Google Cloud Identity Platform). Si aún no tienes tu clave de autenticación de APNS, consulta Configura APNS con FCM para obtener información sobre cómo obtenerla.

    1. Abre Firebase console.

    2. Navega hasta Configuración de proyecto.

    3. Selecciona la pestaña Cloud Messaging.

    4. En Clave de autenticación de APN, en la sección Configuración de app para iOS, haz clic en Subir.

    5. Selecciona tu clave.

    6. Agrega el ID de la clave. Puedes encontrar el ID de la clave en Certificados, identificadores y perfiles en el Centro para miembros de Apple Developer.

    7. Haz clic en Subir.

Si ya tienes un certificado de APN, puedes subirlo para completar el proceso.

Usa la verificación de reCAPTCHA

Para permitir que el SDK de cliente use reCAPTCHA, haz lo siguiente:

  1. Abre la configuración de tu proyecto en Xcode.

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

  3. Selecciona tu app en la sección Targets.

  4. Selecciona la pestaña Info.

  5. Expande la sección URL Types.

  6. Haz clic en el botón +.

  7. Ingresa el ID de cliente revertido en el campo URL Schemes. Puedes encontrar este valor en el archivo de configuración GoogleService-Info.plist como REVERSED_CLIENT_ID.

Cuando se haya completado, la configuración debería ser similar a la siguiente:

Esquemas personalizados

De forma opcional, puedes personalizar la forma en que tu app presenta SFSafariViewController o UIWebView cuando muestra el reCAPTCHA. Para ello, crea una clase personalizada que cumpla con el protocolo FIRAuthUIDelegate y pásala a verifyPhoneNumber:UIDelegate:completion:.

Elige un patrón de inscripción

Puedes elegir si tu app requerirá una autenticación de varios factores, además de cómo y cuándo inscribir a tus usuarios. Entre los patrones comunes, se incluyen los siguientes:

  • Inscribir el segundo factor del usuario como parte del registro. Usa este método si tu app requiere la autenticación de varios factores para todos los usuarios. Considera que una cuenta debe tener una dirección de correo electrónico verificada para inscribir un segundo factor, por lo que tu flujo de registro deberá adaptarse a esto.

  • Ofrecer una opción que se puede omitir para inscribir un segundo factor durante el registro. Es posible que las apps que quieran fomentar el proceso de autenticación de varios factores, pero que no lo requieran, prefieran este enfoque.

  • Proporcionar la capacidad de agregar un segundo factor desde la página de administración de la cuenta o el perfil del usuario, en lugar de la pantalla de registro. Esto minimiza la fricción durante el proceso de registro y, a la vez, permite que la autenticación de varios factores esté disponible para los usuarios sensibles a la seguridad.

  • Requiere agregar un segundo factor de manera incremental cuando el usuario quiera acceder a las funciones con requisitos de seguridad mayores.

Inscribe un segundo factor

Si quieres inscribir un nuevo factor secundario para un usuario, haz lo siguiente:

  1. Vuelve a autenticar al usuario.

  2. Pídele al usuario que ingrese su número de teléfono.

  3. Obtén una sesión de varios factores para el usuario:

    Swift

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

    Objective-C

    [authResult.user.multiFactor
      getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session,
                                NSError * _Nullable error) {
        // ...
    }];
    
  4. Envía un mensaje de verificación al teléfono del usuario. Asegúrate de que el número de teléfono tenga un formato + inicial y no otros signos de puntuación ni espacios en blanco (por ejemplo: +15105551234)

    Swift

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

    Objective-C

    // 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, se recomienda informar con anticipación a los usuarios que recibirán un mensaje SMS y que se aplicarán las tarifas estándar.

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

  5. Después de que se envíe el código SMS, pídele al usuario que lo verifique. Luego, usa su respuesta para compilar un PhoneAuthCredential:

    Swift

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

    Objective-C

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

    Swift

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    Objective-C

    FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  7. Completa la inscripción. De manera opcional, puedes especificar un nombre visible para el segundo factor. Esto resulta útil para usuarios con múltiples segundos factores, ya que el número de teléfono se enmascara durante el flujo de autenticación (por ejemplo, +1******1234).

    Swift

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

    Objective-C

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

En el siguiente código, se muestra un ejemplo completo de la inscripción de un segundo factor:

Swift

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

Objective-C

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

¡Felicitaciones! Registraste correctamente un segundo factor de autenticación para un usuario.

Permite que los usuarios accedan con un segundo factor

Para que un usuario acceda con la verificación mediante SMS de dos factores, haz lo siguiente:

  1. Permite que el usuario acceda con el primer factor y, luego, detecta un error que indique que se requiere una autenticación de varios factores. Este error contiene un agente de resolución, sugerencias sobre los segundos factores inscritos y una sesión subyacente, si el usuario se autenticó correctamente con el primer factor.

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

    Swift

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

    Objective-C

    [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, detecta el error después de llamar a getCredentialWith().

  2. Si el usuario tiene múltiples factores secundarios inscritos, pregúntale cuál usará. Puedes obtener el número de teléfono enmascarado con resolver.hints[selectedIndex].phoneNumber y el nombre visible con resolver.hints[selectedIndex].displayName.

    Swift

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

    Objective-C

    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. Envía un mensaje de verificación al teléfono del usuario:

    Swift

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

    Objective-C

    // 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 que se envíe el código SMS, pídele al usuario que lo verifique y lo use para compilar un PhoneAuthCredential:

    Swift

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

    Objective-C

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

    Swift

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    Objective-C

    FIRMultiFactorAssertion *assertion =
        [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  6. Resuelve el acceso. Luego, puedes acceder al resultado del acceso original, en el que se incluyen los datos específicos del proveedor y las credenciales de autenticación estándar:

    Swift

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

    Objective-C

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

En el siguiente código, se muestra un ejemplo completo de acceso de un usuario de varios factores:

Swift

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

Objective-C

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

¡Felicitaciones! Permitiste acceder a un usuario correctamente mediante la autenticación de varios factores.

¿Qué sigue?