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

En este documento, se muestra cómo agregar una autenticación de varios factores por 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 en plataformas sociales, interceptar un mensaje de texto es más difícil.

Antes de comenzar

  1. Habilita un proveedor que admita la autenticación de varios factores. Estos incluyen:

    • Correo electrónico y contraseña
    • Enviar vínculo por correo electrónico
    • Google
    • Facebook
    • Twitter
    • GitHub
    • Microsoft
    • Yahoo
    • LinkedIn
  2. Asegúrate de que tu app verifique 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, bloqueen al propietario real mediante el agregado de un segundo factor.

Habilita la autenticación de varios factores

  1. Ve a la página MFA de Identity Platform en Cloud Console.
    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. Haga clic en Save.

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 la versión 8.0 de iOS, no necesitas pedirle al usuario que permita las notificaciones push para usar este método.

  • Verificación con 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), puede usar reCAPTCHA. En muchos casos, reCAPTCHA se resolverá de forma automática 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 del proyecto.

    3. Selecciona la pestaña Cloud Messaging.

    4. En Clave de autenticación de APNS, en la sección Configuración de la 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 de miembros desarrolladores de Apple.

    7. Haz clic en Subir.

Si ya tienes un certificado APNS, 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.

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

  4. Selecciona la pestaña Información.

  5. Expande la sección Tipos de URL.

  6. Haz clic en el botón +.

  7. Ingresa el ID de cliente revertido en el campo Esquemas de URL. Puedes encontrar este valor en la lista del 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 el SFSafariViewController o el 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 requiere una autenticación de varios factores, además de cómo y cuándo inscribir a tus usuarios. Algunos patrones comunes incluyen los siguientes:

  • Inscribir el segundo factor del usuario como parte del registro. Usa este método si tu aplicación requiere la autenticación de varios factores para todos los usuarios.

  • 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

Para 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

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

    Objective-C

    [user.multiFactor getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session, NSError * _Nullable error) {
      // ...
    }];
    
  4. Envía un mensaje de verificación al teléfono del usuario:

    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 la notificación push silenciosa no está disponible, se emite un desafío de reCAPTCHA en su lugar.

  5. Una vez que se envía el código SMS, pídele al usuario que verifique el código:

    Swift

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

    Objective-C

    // Ask user for the SMS verification code.
    FIRAuthCredential *credential = [FIRPhoneAuthProvider.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 es útil para usuarios con varios 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.
    [user.multiFactor enrollWithAssertion:assertion
      displayName:displayName
      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: kPhoneSecondFactorVerificationCode)
          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.
      FIRAuthCredential *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 por 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 varios factores secundarios inscritos, pregúntale cuál usar:

    Swift

    // Ask user which second factor to use.
    // You can get the masked phone number via resolver.hints[selectedIndex].phoneNumber
    // You can get the display name via resolver.hints[selectedIndex].displayName
    if (resolver.hints[selectedIndex].factorId == FIRPhoneMultiFactorID) {
      // User selected a phone second factor.
      // ...
    } else {
      // Unsupported second factor.
      // Note that only phone second factors are currently supported.
    }
    

    Objective-C

    FIRMultiFactorResolver *resolver = (FIRMultiFactorResolver *)error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];
    // Ask user which second factor to use.
    FIRPhoneMultiFactorInfo *hint = resolver.hints[selectedIndex];
    
  3. Envía un mensaje de verificación al teléfono del usuario:

    Swift

    // Send SMS verification code.
    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ía el código SMS, pídele al usuario que verifique el código:

    Swift

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

    Objective-C

    // Ask user for the SMS verification code.
    FIRAuthCredential *credential = [FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationID                                                                               verificationCode:kPhoneSecondFactorVerificationCode];
    
  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 == nil || authError!.code != AuthErrorCode.secondFactorRequired.rawValue) {
    // User is not enrolled with a second factor and is successfully signed in.
    // ...
  } else {
    let resolver = authError!.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver
    // Ask user which second factor to use.
    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.
        let credential = PhoneAuthProvider.provider().credential(
          withVerificationID: verificationId!,
          verificationCode: kPhoneSecondFactorVerificationCode)
        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.
    FIRPhoneMultiFactorInfo *hint = 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.
        FIRAuthCredential *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 Accediste correctamente a un usuario mediante la autenticación de varios factores.

Próximos pasos