Adicionar autenticação multifator à sua app Web

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

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. Ative as regiões onde planeia usar a autenticação por SMS. A Identity Platform usa uma política de região de SMS totalmente bloqueadora, que ajuda a criar os seus projetos num estado mais seguro por predefinição.

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

Usar a multilocação

Se estiver a ativar a autenticação multifator para utilização num ambiente de vários inquilinos, certifique-se de que conclui os seguintes passos (além das restantes instruções neste documento):

  1. Na Google Cloud consola, selecione o inquilino com o qual quer trabalhar.

  2. No seu código, defina o campo tenantId na instância Auth para o ID do seu inquilino. Por exemplo:

    Versão Web 9

    import { getAuth } from "firebase/auth";
    
    const auth = getAuth(app);
    auth.tenantId = "myTenantId1";
    

    Versão Web 8

    firebase.auth().tenantId = 'myTenantId1';
    

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.

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.

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

Configurar o validador do reCAPTCHA

Antes de poder enviar códigos por SMS, tem de configurar um validador do reCAPTCHA. A Identity Platform usa o reCAPTCHA para evitar a utilização abusiva, garantindo que os pedidos de validação de números de telefone provêm de um dos domínios permitidos da sua app.

Não precisa de configurar manualmente um cliente do reCAPTCHA. O objeto RecaptchaVerifier do SDK do cliente cria e inicializa automaticamente todas as chaves e segredos do cliente necessários.

Usar o reCAPTCHA invisível

O objeto RecaptchaVerifier suporta o reCAPTCHA invisível, que pode validar frequentemente o utilizador sem exigir qualquer interação. Para usar um reCAPTCHA invisível, crie um RecaptchaVerifier com o parâmetro size definido como invisible e especifique o ID do elemento da IU que inicia a inscrição multifator:

Versão Web 9

import { RecaptchaVerifier, getAuth } from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier(getAuth(), "sign-in-button", {
    "size": "invisible",
    "callback": function(response) {
        // reCAPTCHA solved, you can proceed with
        // phoneAuthProvider.verifyPhoneNumber(...).
        onSolvedRecaptcha();
    }
});

Versão Web 8

var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('sign-in-button', {
'size': 'invisible',
'callback': function(response) {
  // reCAPTCHA solved, you can proceed with phoneAuthProvider.verifyPhoneNumber(...).
  onSolvedRecaptcha();
}
});

Usar o widget reCAPTCHA

Para usar um widget reCAPTCHA visível, crie um elemento HTML para conter o widget e, em seguida, crie um objeto RecaptchaVerifier com o ID do contentor da IU. Opcionalmente, também pode definir callbacks que são invocados quando o reCAPTCHA é resolvido ou expira:

Versão Web 9

import { RecaptchaVerifier, getAuth } from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier(
    getAuth(),
    "recaptcha-container",

    // Optional reCAPTCHA parameters.
    {
      "size": "normal",
      "callback": function(response) {
        // reCAPTCHA solved, you can proceed with
        // phoneAuthProvider.verifyPhoneNumber(...).
        onSolvedRecaptcha();
      },
      "expired-callback": function() {
        // Response expired. Ask user to solve reCAPTCHA again.
        // ...
      }
    }
);

Versão Web 8

var recaptchaVerifier = new firebase.auth.RecaptchaVerifier(
  'recaptcha-container',
  // Optional reCAPTCHA parameters.
  {
    'size': 'normal',
    'callback': function(response) {
      // reCAPTCHA solved, you can proceed with phoneAuthProvider.verifyPhoneNumber(...).
      // ...
      onSolvedRecaptcha();
    },
    'expired-callback': function() {
      // Response expired. Ask user to solve reCAPTCHA again.
      // ...
    }
  });

Pré-renderizar o reCAPTCHA

Opcionalmente, pode pré-renderizar o reCAPTCHA antes de iniciar a inscrição na autenticação de dois fatores:

Versão Web 9

recaptchaVerifier.render()
    .then(function (widgetId) {
        window.recaptchaWidgetId = widgetId;
    });

Versão Web 8

recaptchaVerifier.render()
  .then(function(widgetId) {
    window.recaptchaWidgetId = widgetId;
  });

Depois de render() ser resolvido, recebe o ID do widget do reCAPTCHA, que pode usar para fazer chamadas para a API reCAPTCHA:

var recaptchaResponse = grecaptcha.getResponse(window.recaptchaWidgetId);

O RecaptchaVerifier abstrai esta lógica com o método verify, pelo que não tem de processar a variável grecaptcha diretamente.

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. Inicialize o validador do reCAPTCHA conforme ilustrado na secção anterior. Ignore este passo se já tiver uma instância do RecaptchaVerifier configurada:

    Versão Web 9

    import { RecaptchaVerifier, getAuth } from "firebase/auth";
    
    const recaptchaVerifier = new RecaptchaVerifier(
      getAuth(),'recaptcha-container-id', undefined);
    

    Versão Web 8

    var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
    
  4. Obtenha uma sessão multifator para o utilizador:

    Versão Web 9

    import { multiFactor } from "firebase/auth";
    
    multiFactor(user).getSession().then(function (multiFactorSession) {
        // ...
    });
    

    Versão Web 8

    user.multiFactor.getSession().then(function(multiFactorSession) {
      // ...
    })
    
  5. Inicialize um objeto PhoneInfoOptions com o número de telefone do utilizador e a sessão multifator:

    Versão Web 9

    // Specify the phone number and pass the MFA session.
    const phoneInfoOptions = {
      phoneNumber: phoneNumber,
      session: multiFactorSession
    };
    

    Versão Web 8

    // Specify the phone number and pass the MFA session.
    var phoneInfoOptions = {
      phoneNumber: phoneNumber,
      session: multiFactorSession
    };
    
  6. Envie uma mensagem de validação para o telemóvel do utilizador:

    Versão Web 9

    import { PhoneAuthProvider } from "firebase/auth";
    
    const phoneAuthProvider = new PhoneAuthProvider(auth);
    phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
        .then(function (verificationId) {
            // verificationId will be needed to complete enrollment.
        });
    

    Versão Web 8

    var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
    // Send SMS verification code.
    return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
      .then(function(verificationId) {
        // 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.

  7. Se o pedido falhar, reponha o reCAPTCHA e, em seguida, repita o passo anterior para que o utilizador possa tentar novamente. Tenha em atenção que verifyPhoneNumber() repõe automaticamente o reCAPTCHA quando gera um erro, uma vez que os tokens do reCAPTCHA são de utilização única.

    Versão Web 9

    recaptchaVerifier.clear();
    

    Versão Web 8

    recaptchaVerifier.clear();
    
  8. Depois de o código SMS ser enviado, peça ao utilizador para validar o código:

    Versão Web 9

    // Ask user for the verification code. Then:
    const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
    

    Versão Web 8

    // Ask user for the verification code. Then:
    var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
    
  9. Inicialize um objeto MultiFactorAssertion com o PhoneAuthCredential:

    Versão Web 9

    import { PhoneMultiFactorGenerator } from "firebase/auth";
    
    const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
    

    Versão Web 8

    var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
    
  10. 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).

    Versão Web 9

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    multiFactor(user).enroll(multiFactorAssertion, "My personal phone number");
    

    Versão Web 8

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    user.multiFactor.enroll(multiFactorAssertion, 'My personal phone number');
    

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

Versão Web 9

import {
    multiFactor, PhoneAuthProvider, PhoneMultiFactorGenerator,
    RecaptchaVerifier, getAuth
} from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier(getAuth(),
    'recaptcha-container-id', undefined);
multiFactor(user).getSession()
    .then(function (multiFactorSession) {
        // Specify the phone number and pass the MFA session.
        const phoneInfoOptions = {
            phoneNumber: phoneNumber,
            session: multiFactorSession
        };

        const phoneAuthProvider = new PhoneAuthProvider(auth);

        // Send SMS verification code.
        return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier);
    }).then(function (verificationId) {
        // Ask user for the verification code. Then:
        const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
        const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);

        // Complete enrollment.
        return multiFactor(user).enroll(multiFactorAssertion, mfaDisplayName);
    });

Versão Web 8

var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
user.multiFactor.getSession().then(function(multiFactorSession) {
  // Specify the phone number and pass the MFA session.
  var phoneInfoOptions = {
    phoneNumber: phoneNumber,
    session: multiFactorSession
  };
  var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
  // Send SMS verification code.
  return phoneAuthProvider.verifyPhoneNumber(
      phoneInfoOptions, recaptchaVerifier);
})
.then(function(verificationId) {
  // Ask user for the verification code.
  var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
  var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
  // Complete enrollment.
  return user.multiFactor.enroll(multiFactorAssertion, mfaDisplayName);
});

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 o erro auth/multi-factor-auth-required. Este erro contém um resolvedor, sugestões sobre os segundos fatores inscritos e uma sessão subjacente que prova que o utilizador fez a autenticação com êxito com o primeiro fator.

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

    Versão Web 9

    import { getAuth, signInWithEmailAndPassword, getMultiFactorResolver} from "firebase/auth";
    
    const auth = getAuth();
    signInWithEmailAndPassword(auth, email, password)
        .then(function (userCredential) {
            // User successfully signed in and is not enrolled with a second factor.
        })
        .catch(function (error) {
            if (error.code == 'auth/multi-factor-auth-required') {
                // The user is a multi-factor user. Second factor challenge is required.
                resolver = getMultiFactorResolver(auth, error);
                // ...
            } else if (error.code == 'auth/wrong-password') {
                // Handle other errors such as wrong password.
            }
    });
    

    Versão Web 8

    firebase.auth().signInWithEmailAndPassword(email, password)
      .then(function(userCredential) {
        // User successfully signed in and is not enrolled with a second factor.
      })
      .catch(function(error) {
        if (error.code == 'auth/multi-factor-auth-required') {
          // The user is a multi-factor user. Second factor challenge is required.
          resolver = error.resolver;
          // ...
        } else if (error.code == 'auth/wrong-password') {
          // Handle other errors such as wrong password.
        } ...
      });
    

    Se o primeiro fator do utilizador for um fornecedor federado, como o OAuth, o SAML ou o OIDC, intercete o erro após chamar signInWithPopup() ou signInWithRedirect().

  2. Se o utilizador tiver vários fatores secundários inscritos, pergunte-lhe qual quer usar:

    Versão Web 9

    // 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 ===
        PhoneMultiFactorGenerator.FACTOR_ID) {
        // User selected a phone second factor.
        // ...
    } else if (resolver.hints[selectedIndex].factorId ===
               TotpMultiFactorGenerator.FACTOR_ID) {
        // User selected a TOTP second factor.
        // ...
    } else {
        // Unsupported second factor.
    }
    

    Versão Web 8

    // 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 === firebase.auth.PhoneMultiFactorGenerator.FACTOR_ID) {
      // User selected a phone second factor.
      // ...
    } else if (resolver.hints[selectedIndex].factorId === firebase.auth.TotpMultiFactorGenerator.FACTOR_ID) {
      // User selected a TOTP second factor.
      // ...
    } else {
      // Unsupported second factor.
    }
    
  3. Inicialize o validador do reCAPTCHA conforme ilustrado na secção anterior. Ignore este passo se já tiver uma instância do RecaptchaVerifier configurada:

    Versão Web 9

    import { RecaptchaVerifier, getAuth } from "firebase/auth";
    
    recaptchaVerifier = new RecaptchaVerifier(getAuth(),
        'recaptcha-container-id', undefined);
    

    Versão Web 8

    var recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container-id');
    
  4. Inicialize um objeto PhoneInfoOptions com o número de telefone do utilizador e a sessão de autenticação multifator. Estes valores estão contidos no objeto resolver transmitido ao erro auth/multi-factor-auth-required:

    Versão Web 9

    const phoneInfoOptions = {
        multiFactorHint: resolver.hints[selectedIndex],
        session: resolver.session
    };
    

    Versão Web 8

    var phoneInfoOptions = {
      multiFactorHint: resolver.hints[selectedIndex],
      session: resolver.session
    };
    
  5. Envie uma mensagem de validação para o telemóvel do utilizador:

    Versão Web 9

    // Send SMS verification code.
    const phoneAuthProvider = new PhoneAuthProvider(auth);
    phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
        .then(function (verificationId) {
            // verificationId will be needed for sign-in completion.
        });
    

    Versão Web 8

    var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
    // Send SMS verification code.
    return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
      .then(function(verificationId) {
        // verificationId will be needed for sign-in completion.
      })
    
  6. Se o pedido falhar, reponha o reCAPTCHA e, em seguida, repita o passo anterior para que o utilizador possa tentar novamente:

    Versão Web 9

    recaptchaVerifier.clear();
    

    Versão Web 8

    recaptchaVerifier.clear();
    
  7. Depois de o código SMS ser enviado, peça ao utilizador para validar o código:

    Versão Web 9

    const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
    

    Versão Web 8

    // Ask user for the verification code. Then:
    var cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode);
    
  8. Inicialize um objeto MultiFactorAssertion com o PhoneAuthCredential:

    Versão Web 9

    const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
    

    Versão Web 8

    var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
    
  9. Ligue para o número resolver.resolveSignIn() para concluir a autenticação secundária. 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:

    Versão Web 9

    // Complete sign-in. This will also trigger the Auth state listeners.
    resolver.resolveSignIn(multiFactorAssertion)
        .then(function (userCredential) {
            // userCredential 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,
            // userCredential.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.
        });
    

    Versão Web 8

    // Complete sign-in. This will also trigger the Auth state listeners.
    resolver.resolveSignIn(multiFactorAssertion)
      .then(function(userCredential) {
        // userCredential 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,
        // userCredential.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.
      });
    

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

Versão Web 9

import {
    getAuth,
    getMultiFactorResolver,
    PhoneAuthProvider,
    PhoneMultiFactorGenerator,
    RecaptchaVerifier,
    signInWithEmailAndPassword
} from "firebase/auth";

const recaptchaVerifier = new RecaptchaVerifier(getAuth(),
    'recaptcha-container-id', undefined);

const auth = getAuth();
signInWithEmailAndPassword(auth, email, password)
    .then(function (userCredential) {
        // User is not enrolled with a second factor and is successfully
        // signed in.
        // ...
    })
    .catch(function (error) {
        if (error.code == 'auth/multi-factor-auth-required') {
            const resolver = getMultiFactorResolver(auth, error);
            // Ask user which second factor to use.
            if (resolver.hints[selectedIndex].factorId ===
                PhoneMultiFactorGenerator.FACTOR_ID) {
                const phoneInfoOptions = {
                    multiFactorHint: resolver.hints[selectedIndex],
                    session: resolver.session
                };
                const phoneAuthProvider = new PhoneAuthProvider(auth);
                // Send SMS verification code
                return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
                    .then(function (verificationId) {
                        // Ask user for the SMS verification code. Then:
                        const cred = PhoneAuthProvider.credential(
                            verificationId, verificationCode);
                        const multiFactorAssertion =
                            PhoneMultiFactorGenerator.assertion(cred);
                        // Complete sign-in.
                        return resolver.resolveSignIn(multiFactorAssertion)
                    })
                    .then(function (userCredential) {
                        // User successfully signed in with the second factor phone number.
                    });
            } else if (resolver.hints[selectedIndex].factorId ===
                       TotpMultiFactorGenerator.FACTOR_ID) {
                // Handle TOTP MFA.
                // ...
            } else {
                // Unsupported second factor.
            }
        } else if (error.code == 'auth/wrong-password') {
            // Handle other errors such as wrong password.
        }
    });

Versão Web 8

var resolver;
firebase.auth().signInWithEmailAndPassword(email, password)
  .then(function(userCredential) {
    // User is not enrolled with a second factor and is successfully signed in.
    // ...
  })
  .catch(function(error) {
    if (error.code == 'auth/multi-factor-auth-required') {
      resolver = error.resolver;
      // Ask user which second factor to use.
      if (resolver.hints[selectedIndex].factorId ===
          firebase.auth.PhoneMultiFactorGenerator.FACTOR_ID) {
        var phoneInfoOptions = {
          multiFactorHint: resolver.hints[selectedIndex],
          session: resolver.session
        };
        var phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
        // Send SMS verification code
        return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
          .then(function(verificationId) {
            // Ask user for the SMS verification code.
            var cred = firebase.auth.PhoneAuthProvider.credential(
                verificationId, verificationCode);
            var multiFactorAssertion =
                firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
            // Complete sign-in.
            return resolver.resolveSignIn(multiFactorAssertion)
          })
          .then(function(userCredential) {
            // User successfully signed in with the second factor phone number.
          });
      } else if (resolver.hints[selectedIndex].factorId ===
        firebase.auth.TotpMultiFactorGenerator.FACTOR_ID) {
        // Handle TOTP MFA.
        // ...
      } else {
        // Unsupported second factor.
      }
    } else if (error.code == 'auth/wrong-password') {
      // Handle other errors such as wrong password.
    } ...
  });

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

O que se segue?