Como adicionar a autenticação multifator ao seu app para iOS

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

A autenticação multifator aumenta a segurança do app. Os invasores costumam comprometer as senhas e contas de redes sociais, mas é mais difícil interceptar uma mensagem de texto.

Antes de começar

  1. Ative pelo menos um provedor compatível com a autenticação multifator. Todos os provedores são compatíveis com a autenticação multifator (MFA), exceto as autenticações por smartphone e a anônima e o Apple Game Center.

  2. Confirme se o app está verificando os e-mails do usuário. A autenticação multifator (MFA) requer verificação de e-mail. Isso impede que agentes mal-intencionados se registrem em um serviço com um e-mail que não tenham e bloqueiem o proprietário real adicionando um segundo fator.

Como ativar a autenticação multifator

  1. Acesse a página MFA do Identity Platform no console do Google Cloud.
    Acessar a página MFA

  2. Na caixa Autenticação de vários fatores baseada em SMS, clique em Ativar.

  3. Insira os números de telefone com os quais você testará seu aplicativo. Ainda que seja opcional, é altamente recomendável registrar números de telefone de teste para evitar a limitação durante o desenvolvimento.

  4. Se você ainda não autorizou o domínio do seu aplicativo, adicione-o à lista de permissões clicando em Adicionar domínio à direita.

  5. Clique em Save.

Como verificar seu aplicativo

O Identity Platform precisa verificar se as solicitações de SMS são provenientes do app. É possível fazer isso de duas maneiras:

  • Notificações de APNs silenciosas: quando você faz login em um usuário pela primeira vez, o Identity Platform pode enviar uma notificação push silenciosa para o dispositivo do usuário. A autenticação poderá continuar se o app receber a notificação. No iOS 8.0 e versões mais recentes, não é necessário solicitar ao usuário que permita notificações push para usar esse método.

  • Verificação reCAPTCHA: se não for possível enviar uma notificação silenciosa (por exemplo, porque o usuário desativou a atualização em segundo plano ou você está testando seu app no simulador do iOS), use o reCAPTCHA. Em muitos casos, o reCAPTCHA será resolvido automaticamente sem interação do usuário.

Como usar notificações silenciosas

Para ativar notificações de APNs para uso com o Identity Platform:

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

  2. Faça upload da chave de autenticação de APNs usando o Console do Firebase. Suas alterações serão automaticamente transferidas para o Google Cloud Identity Platform. Se você ainda não tem a chave de autenticação de APNs, consulte Como configurar APNs com o FCM para saber como recebê-la.

    1. Abra o Console do Firebase.

    2. Navegue até Configurações do projeto.

    3. Selecione a guia Cloud Messaging.

    4. Em Chave de autenticação de APNs, na seção Configuração do app para iOS, clique em Fazer upload.

    5. Selecione a chave.

    6. Adicione o ID da chave. Encontre o ID da chave em Certificados, identificadores e perfis na Apple Developer Member Center.

    7. Clique em Upload.

Se você já tem um certificado de APNs, é possível fazer o upload dele em vez disso.

Como usar a verificação reCAPTCHA

Para ativar o SDK do cliente e usar o reCAPTCHA. faça o seguinte:

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

  2. Clique duas vezes no nome do projeto na visualização em árvore à esquerda.

  3. Selecione seu app na seção Destinos.

  4. Selecione a guia Informações.

  5. Expanda a seção Tipos de URL.

  6. Clique no botão +.

  7. Insira o ID do cliente revertido no campo URL esquemas de gerenciamento. Esse valor está listado no arquivo de configuração GoogleService-Info.plist como REVERSED_CLIENT_ID.

Quando concluída, a configuração será semelhante a esta:

Custom schemes

Também é possível personalizar a forma como o app apresenta o SFSafariViewController ou o UIWebView ao exibir o reCAPTCHA. Para fazer isso, crie uma classe personalizada que esteja em conformidade com o protocolo FIRAuthUIDelegate e transmita-a para verifyPhoneNumber:UIDelegate:completion:.

Como escolher um padrão de inscrição

É possível escolher se o app requer autenticação multifator e como e quando registrar os usuários. Alguns padrões comuns incluem:

  • Registrar o segundo fator do usuário como parte do processo. Use esse método se o app exigir autenticação multifator para todos os usuários. Observe que uma conta precisa ter um endereço de e-mail verificado para registrar um segundo fator. Portanto, seu fluxo de registro terá que acomodar isso.

  • Oferecer uma opção que pode ser ignorada para registrar um segundo fator durante o processo. Os apps que quiserem incentivar, mas não exigirem, a autenticação multifator terão essa abordagem.

  • Permitir a adição de um segundo fator na página de gerenciamento da conta ou no perfil do usuário, em vez da tela de inscrição. Isso minimiza o atrito durante o processo de registro, ao mesmo tempo que disponibiliza a autenticação multifator para usuários que se preocupam com a segurança.

  • Exigir a adição de um segundo fator de maneira incremental quando o usuário quiser acessar recursos com requisitos de segurança aprimorados.

Como registrar um segundo fator

Para registrar um novo fator secundário para um usuário:

  1. Reautentique o usuário.

  2. Peça ao usuário para inserir o número de telefone.

  3. Receba uma sessão de vários fatores para o usuário:

    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 verificação ao smartphone do usuário. Verifique se o número de telefone está formatado com + no começo e sem outros sinais de pontuação ou espaços 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.
    }];
    

    Ainda que não seja obrigatório, é uma prática recomendada informar aos usuários antecipadamente que eles receberão uma mensagem SMS e quais taxas padrão serão aplicadas.

    O método verifyPhoneNumber() inicia o processo de verificação do app em segundo plano usando a notificação push silenciosa. Se a notificação push silenciosa não estiver disponível, um desafio reCAPTCHA será emitido.

  5. Após o envio do código SMS, peça ao usuário para fazer a verificação. Em seguida, use a resposta dele 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. Se quiser, especifique um nome de exibição para o segundo fator. Isso é útil para usuários com vários fatores, já que o número de telefone é mascarado 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! Você registrou um segundo fator de autenticação para um usuário.

Como fazer login dos usuários com um segundo fator

Para fazer login de um usuário com verificação por SMS de dois fatores:

  1. Faça o login do usuário com o primeiro fator e, em seguida, capture um erro indicando que a autenticação multifator é necessária. Esse erro contém um resolvedor, dicas sobre os dois fatores registrados e uma sessão subjacente que mostra que o usuário foi autenticado com êxito com o primeiro fator.

    Por exemplo, se o primeiro fator do usuário for um e-mail e uma senha:

    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 usuário for um provedor federado, como o OAuth, capture o erro depois de chamar getCredentialWith().

  2. Se o usuário tiver vários fatores secundários registrados, pergunte a ele qual usar. É possível conseguir o número de telefone mascarado com resolver.hints[selectedIndex].phoneNumber e o nome de exibição 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. Enviar uma mensagem de verificação para o telefone do usuário:

    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. Depois que o código SMS for enviado, peça ao usuário para verificar 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 login. Em seguida, você pode acessar o resultado do login original, que inclui os dados padrão do provedor 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 login em um usuário de vários fatores:

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! Você fez login com sucesso usando uma autenticação multifator.

A seguir