Adicionar autenticação multifator à sua app iOS

Este documento mostra como adicionar a autenticação multifator por SMS à sua app iOS.

A autenticação multifator aumenta a segurança da sua app. Embora os atacantes comprometam frequentemente palavras-passe e contas sociais, intercetar uma mensagem de texto é mais difícil.

Antes de começar

  1. Ative, pelo menos, um fornecedor que suporte a autenticação multifator. Todos os fornecedores suportam a MFA, exceto a autenticação por telefone, a autenticação anónima e o Game Center da Apple.

  2. Certifique-se de que a sua app está a validar os emails dos utilizadores. A MFA requer validação por email. Isto impede que pessoas com intenções maliciosas se registem num serviço com um email que não lhes pertence e, em seguida, bloqueiem o proprietário real adicionando um segundo fator.

Ativar a autenticação multifator

  1. Aceda à página MFA do Identity Platform na Google Cloud consola.
    Aceda à página da MFA

  2. Na caixa com o título Autenticação multifator baseada em SMS, clique em Ativar.

  3. Introduza os números de telefone com os quais vai testar a sua app. Embora seja opcional, é vivamente recomendada a registo de números de telefone de teste para evitar a limitação durante o desenvolvimento.

  4. Se ainda não tiver autorizado o domínio da sua app, adicione-o à lista de autorizações clicando em Adicionar domínio à direita.

  5. Clique em Guardar.

Validar a sua app

A Identity Platform tem de validar se os pedidos de SMS estão a ser enviados a partir da sua app. Pode fazê-lo de duas formas:

  • Notificações APNs silenciosas: quando um utilizador inicia sessão pela primeira vez, a Identity Platform pode enviar uma notificação push silenciosa para o dispositivo do utilizador. A autenticação pode continuar se a app receber a notificação. Tenha em atenção que, a partir do iOS 8.0, não precisa de pedir ao utilizador que permita as notificações push para usar este método.

  • Validação reCAPTCHA: se não conseguir enviar uma notificação silenciosa (por exemplo, porque o utilizador desativou a atualização em segundo plano ou está a testar a sua app no simulador do iOS), pode usar o reCAPTCHA. Em muitos casos, o reCAPTCHA resolve-se automaticamente sem interação do utilizador.

Usar notificações silenciosas

Para ativar as notificações APNs para utilização com a Identity Platform:

  1. No Xcode, ative as notificações push para o seu projeto.

  2. Carregue a chave de autenticação dos APNs através da consola do Firebase (as alterações são automaticamente transferidas para a Google Cloud Identity Platform). Se ainda não tiver a chave de autenticação do APNs, consulte o artigo Configurar o APNs com o FCM para saber como obtê-la.

    1. Abra a consola do Firebase.

    2. Navegue para Definições do projeto.

    3. Selecione o separador Cloud Messaging.

    4. Em Chave de autenticação APNs, na secção Configuração da app iOS, clique em Carregar para carregar a chave de autenticação de desenvolvimento, a chave de autenticação de produção ou ambas. É necessário, no mínimo, um desses dados.

    5. Selecione a chave.

    6. Adicione o ID da chave. Pode encontrar o ID da chave em Certificados, identificadores e perfis no Apple Developer Member Center.

    7. Clique em Carregar.

Se já tiver um certificado de APNs, pode carregá-lo.

Usar a validação reCAPTCHA

Para permitir que o SDK de cliente use o reCAPTCHA:

  1. Abra a configuração do projeto no Xcode.

  2. Clique duas vezes no nome do projeto na vista de árvore do lado esquerdo.

  3. Selecione a sua app na secção Segmentos.

  4. Selecione o separador Informações.

  5. Expanda a secção Tipos de URLs.

  6. Clique no botão +.

  7. Introduza o ID de cliente invertido no campo Esquemas de URL. Pode encontrar este valor indicado no ficheiro de configuração GoogleService-Info.plist como REVERSED_CLIENT_ID.

Quando estiver concluída, a configuração deve ser semelhante à seguinte:

Esquemas personalizados

Opcionalmente, pode personalizar a forma como a sua app apresenta o ícone SFSafariViewController ou UIWebView quando apresenta o reCAPTCHA. Para o fazer, crie uma classe personalizada em conformidade com o protocolo FIRAuthUIDelegate e transmita-a para verifyPhoneNumber:UIDelegate:completion:.

Escolher um padrão de inscrição

Pode escolher se a sua app requer autenticação multifator e como e quando inscrever os seus utilizadores. Alguns padrões comuns incluem:

  • Inscrever o segundo fator do utilizador como parte do registo. Use este método se a sua app exigir a autenticação multifator para todos os utilizadores. Tenha em atenção que uma conta tem de ter um endereço de email validado para inscrever um segundo fator, pelo que o seu fluxo de registo tem de ter isto em conta.

  • Ofereça uma opção ignorável para inscrever um segundo fator durante o registo. As apps que querem incentivar, mas não exigir, a autenticação multifator podem preferir esta abordagem.

  • Oferecer a capacidade de adicionar um segundo fator a partir da página de gestão do perfil ou da conta do utilizador, em vez do ecrã de inscrição. Isto minimiza as complicações durante o processo de registo, ao mesmo tempo que disponibiliza a autenticação multifator para utilizadores sensíveis à segurança.

  • Exigir a adição de um segundo fator de forma incremental quando o utilizador quiser aceder a funcionalidades com requisitos de segurança aumentados.

Inscrever um segundo fator

Para inscrever um novo fator secundário para um utilizador:

  1. Volte a autenticar o utilizador.

  2. Peça ao utilizador para introduzir o respetivo número de telefone.

  3. Obtenha uma sessão multifator para o utilizador:

    Swift

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

    Objective-C

    [authResult.user.multiFactor
      getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session,
                                NSError * _Nullable error) {
        // ...
    }];
    
  4. Envie uma mensagem de validação para o telemóvel do utilizador. Certifique-se de que o número de telefone está formatado com um + inicial e sem outra pontuação ou espaço em branco (por exemplo: +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.
    }];
    

    Embora não seja obrigatório, é uma prática recomendada informar os utilizadores antecipadamente de que vão receber uma mensagem SMS e que se aplicam as tarifas padrão.

    O método verifyPhoneNumber() inicia o processo de validação de apps em segundo plano através de uma notificação push silenciosa. Se a notificação push silenciosa não estiver disponível,é emitido um desafio reCAPTCHA.

  5. Depois de o código SMS ser enviado, peça ao utilizador para validar o código. Em seguida, use a resposta para criar um 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. Inicialize um objeto de declaração:

    Swift

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    Objective-C

    FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  7. Conclua a inscrição. Opcionalmente, pode especificar um nome a apresentar para o segundo fator. Isto é útil para utilizadores com vários segundos fatores, uma vez que o número de telefone é ocultado durante o fluxo de autenticação (por exemplo, +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) {
        // ...
    }];
    

O código abaixo mostra um exemplo completo de inscrição de um segundo fator:

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

Parabéns! Registou com êxito um segundo fator de autenticação para um utilizador.

Iniciar sessão dos utilizadores com um segundo fator

Para iniciar sessão de um utilizador com a validação por SMS de dois fatores:

  1. Inicie sessão no utilizador com o primeiro fator e, em seguida, detete um erro que indique que é necessária a autenticação multifator. Este erro contém um resolvedor, sugestões sobre os segundos fatores inscritos e uma sessão subjacente que prova que o utilizador efetuou a autenticação com êxito com o primeiro fator.

    Por exemplo, se o primeiro fator do utilizador foi um email e uma palavra-passe:

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

    Se o primeiro fator do utilizador for um fornecedor federado, como o OAuth, intercete o erro após chamar getCredentialWith().

  2. Se o utilizador tiver vários fatores secundários inscritos, pergunte-lhe qual quer usar. Pode obter o número de telefone oculto com resolver.hints[selectedIndex].phoneNumber e o nome a apresentar com 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. Envie uma mensagem de validação para o telemóvel do utilizador:

    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. Assim que o código por SMS for enviado, peça ao utilizador para validar o código e usá-lo para criar um 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. Inicialize um objeto de declaração com a credencial:

    Swift

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    Objective-C

    FIRMultiFactorAssertion *assertion =
        [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  6. Resolva o início de sessão. Em seguida, pode aceder ao resultado do início de sessão original, que inclui os dados específicos do fornecedor padrão e as credenciais de autenticação:

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

O código abaixo mostra um exemplo completo de início de sessão de um utilizador com autenticação multifator:

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

Parabéns! Iniciou sessão com êxito num utilizador através da autenticação multifator.

O que se segue?