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
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.
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.
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):
Na Google Cloud consola, selecione o inquilino com o qual quer trabalhar.
No seu código, defina o campo
tenantIdna instânciaAuthpara 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
Aceda à página MFA do Identity Platform na Google Cloud consola.
Aceda à página da MFANa caixa com o título Autenticação multifator baseada em SMS, clique em Ativar.
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.
Se ainda não tiver autorizado o domínio da sua app, adicione-o à lista de autorizações clicando em Adicionar domínio à direita.
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:
Volte a autenticar o utilizador.
Peça ao utilizador para introduzir o respetivo número de telefone.
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');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) { // ... })Inicialize um objeto
PhoneInfoOptionscom 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 };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.
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();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);Inicialize um objeto
MultiFactorAssertioncom oPhoneAuthCredential: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);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:
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()ousignInWithRedirect().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. }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');Inicialize um objeto
PhoneInfoOptionscom o número de telefone do utilizador e a sessão de autenticação multifator. Estes valores estão contidos no objetoresolvertransmitido ao erroauth/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 };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. })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();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);Inicialize um objeto
MultiFactorAssertioncom oPhoneAuthCredential:Versão Web 9
const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);Versão Web 8
var multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);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?
- Faça a gestão de utilizadores com autenticação multifator de forma programática com o SDK de administrador.