Autenticar com o Firebase usando link por e-mail no JavaScript

Use o Firebase Authentication para fazer o login de um usuário por meio de um link enviado a ele por e-mail. Nesse processo, o endereço de e-mail do usuário também é verificado.

O login por e-mail tem inúmeros benefícios:

  • Inscrição e login simplificados.
  • Menos chances de reutilizar senhas entre os aplicativos, o que pode diminuir a segurança até mesmo das senhas mais fortes.
  • Capacidade de autenticar o usuário e verificar se ele é o legítimo proprietário do endereço de e-mail.
  • O usuário só precisa de uma conta de e-mail acessível para fazer login. Não são necessários números de telefone nem contas em redes sociais.
  • O usuário faz login com segurança sem precisar fornecer ou lembrar de uma senha. Em dispositivos móveis, isso é bem importante porque facilita a vida do usuário.
  • O usuário que já tiver feito login com um identificador de e-mail (senha ou federado) poderá se conectar depois apenas com o e-mail. Por exemplo, se ele se esquecer da senha, ainda será possível fazer login sem precisar redefini-la.

Antes de começar

Copie o snippet de inicialização do Console do Firebase para o projeto, conforme descrito na página Adicionar o Firebase a seu projeto do JavaScript, se ainda não tiver feito isso.

Para que os usuários façam login usando um link enviado por e-mail, primeiro ative o provedor de e-mail e o método de login por link no projeto do Firebase:

  1. No Console do Firebase, abra a seção Auth.
  2. Na guia Método de login, ative o provedor de E-mail/senha. Ative esse método para poder usar o login por link de e-mail.
  3. Na mesma seção, ative o método Link do e-mail (login sem senha).
  4. Clique em Salvar.

Para iniciar o fluxo de autenticação, apresente ao usuário uma interface que solicite que ele forneça seu endereço de e-mail e, em seguida, chame sendSignInLinkToEmail para solicitar que o Firebase envie o link de autenticação para o e-mail do usuário.

  1. Crie o objeto ActionCodeSettings, que fornece ao Firebase instruções sobre como criar o link de e-mail. Defina os seguintes campos:

    • url: o link direto para incorporar e qualquer estado adicional a ser transmitido. É preciso que o domínio do link esteja adicionado na lista de domínios autorizados do Console do Firebase, encontrada na guia "Método de login" (Autenticação -> Método de login).
    • android e ios: os aplicativos que devem ser usados quando o link para login é aberto em um dispositivo Android ou Apple. Saiba mais sobre como configurar o Firebase Dynamic Links para abrir links de ação por e-mail via apps para dispositivos móveis.
    • handleCodeInApp: definido como verdadeiro. É necessário sempre concluir o processo de login no app, ao contrário de outras ações fora de banda, como redefinição de senha e verificação de e-mail. Isso acontece porque o usuário precisa estar conectado e com o estado Auth mantido no app ao final do fluxo.
    • dynamicLinkDomain: quando vários domínios de link dinâmico personalizados forem definidos para um projeto, especifique qual deles usar quando o link for aberto por um app específico para dispositivos móveis (por exemplo, example.page.link). Caso contrário, o primeiro domínio será selecionado automaticamente.

      API modular da Web

      const actionCodeSettings = {
        // URL you want to redirect back to. The domain (www.example.com) for this
        // URL must be in the authorized domains list in the Firebase Console.
        url: 'https://www.example.com/finishSignUp?cartId=1234',
        // This must be true.
        handleCodeInApp: true,
        iOS: {
          bundleId: 'com.example.ios'
        },
        android: {
          packageName: 'com.example.android',
          installApp: true,
          minimumVersion: '12'
        },
        dynamicLinkDomain: 'example.page.link'
      };

      API com namespace da Web

      var actionCodeSettings = {
        // URL you want to redirect back to. The domain (www.example.com) for this
        // URL must be in the authorized domains list in the Firebase Console.
        url: 'https://www.example.com/finishSignUp?cartId=1234',
        // This must be true.
        handleCodeInApp: true,
        iOS: {
          bundleId: 'com.example.ios'
        },
        android: {
          packageName: 'com.example.android',
          installApp: true,
          minimumVersion: '12'
        },
        dynamicLinkDomain: 'example.page.link'
      };

    Para saber mais sobre o ActionCodeSettings, consulte a seção Como transmitir o estado nas ações de e-mail.

  2. Solicite o e-mail ao usuário.

  3. Envie o link de autenticação a esse endereço e salve o e-mail caso o usuário conclua o login no mesmo dispositivo.

    API modular da Web

    import { getAuth, sendSignInLinkToEmail } from "firebase/auth";
    
    const auth = getAuth();
    sendSignInLinkToEmail(auth, email, actionCodeSettings)
      .then(() => {
        // The link was successfully sent. Inform the user.
        // Save the email locally so you don't need to ask the user for it again
        // if they open the link on the same device.
        window.localStorage.setItem('emailForSignIn', email);
        // ...
      })
      .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        // ...
      });

    API com namespace da Web

    firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings)
      .then(() => {
        // The link was successfully sent. Inform the user.
        // Save the email locally so you don't need to ask the user for it again
        // if they open the link on the same device.
        window.localStorage.setItem('emailForSignIn', email);
        // ...
      })
      .catch((error) => {
        var errorCode = error.code;
        var errorMessage = error.message;
        // ...
      });

Preocupações com segurança

No Firebase Auth, o usuário precisa fornecer o endereço de e-mail ao concluir o fluxo de login. Isso evita que o link seja usado para fazer o login de um usuário indesejado ou em um dispositivo não intencional. Para fazer login, esse endereço precisa ser igual ao e-mail que recebeu o link.

É possível simplificar o fluxo dos usuários que abrem o link no mesmo dispositivo usado para solicitá-lo. Basta armazenar o endereço deles localmente quando enviar o e-mail de login. Por exemplo, você pode usar o localStorage ou cookies. Depois, utilize o endereço para concluir o fluxo. Não transmita o e-mail do usuário nos parâmetros de URL de redirecionamento e nem o reutilize, já que isso pode ativar injeções de sessão.

Após a conclusão do login, qualquer mecanismo anterior de login não verificado será removido do usuário, e todas as sessões atuais serão invalidadas. Por exemplo, se alguém tiver criado uma conta não verificada com a mesma senha e e-mail, a senha do usuário será removida. Isso impede que a pessoa que reivindicou a propriedade e criou essa conta faça login novamente com o e-mail e senha não confirmados.

Além disso, não deixe de usar um URL HTTPS na produção para impedir que o link possa ser interceptado por servidores intermediários.

Como concluir o login em uma página da Web

O formato do link direto do e-mail é igual ao usado em ações fora de banda, como verificação de e-mail, redefinição de senha e revogação de alterações. No Firebase Auth, essa verificação é simplificada pelo fornecimento da API isSignInWithEmailLink que confere se o link é para login por e-mail.

Para concluir o login em uma página de destino, chame signInWithEmailLink com o e-mail do usuário e o link real que contém o código de uso único.

API modular da Web

import { getAuth, isSignInWithEmailLink, signInWithEmailLink } from "firebase/auth";

// Confirm the link is a sign-in with email link.
const auth = getAuth();
if (isSignInWithEmailLink(auth, window.location.href)) {
  // Additional state parameters can also be passed via URL.
  // This can be used to continue the user's intended action before triggering
  // the sign-in operation.
  // Get the email if available. This should be available if the user completes
  // the flow on the same device where they started it.
  let email = window.localStorage.getItem('emailForSignIn');
  if (!email) {
    // User opened the link on a different device. To prevent session fixation
    // attacks, ask the user to provide the associated email again. For example:
    email = window.prompt('Please provide your email for confirmation');
  }
  // The client SDK will parse the code from the link for you.
  signInWithEmailLink(auth, email, window.location.href)
    .then((result) => {
      // Clear email from storage.
      window.localStorage.removeItem('emailForSignIn');
      // You can access the new user via result.user
      // Additional user info profile not available via:
      // result.additionalUserInfo.profile == null
      // You can check if the user is new or existing:
      // result.additionalUserInfo.isNewUser
    })
    .catch((error) => {
      // Some error occurred, you can inspect the code: error.code
      // Common errors could be invalid email and invalid or expired OTPs.
    });
}

API com namespace da Web

// Confirm the link is a sign-in with email link.
if (firebase.auth().isSignInWithEmailLink(window.location.href)) {
  // Additional state parameters can also be passed via URL.
  // This can be used to continue the user's intended action before triggering
  // the sign-in operation.
  // Get the email if available. This should be available if the user completes
  // the flow on the same device where they started it.
  var email = window.localStorage.getItem('emailForSignIn');
  if (!email) {
    // User opened the link on a different device. To prevent session fixation
    // attacks, ask the user to provide the associated email again. For example:
    email = window.prompt('Please provide your email for confirmation');
  }
  // The client SDK will parse the code from the link for you.
  firebase.auth().signInWithEmailLink(email, window.location.href)
    .then((result) => {
      // Clear email from storage.
      window.localStorage.removeItem('emailForSignIn');
      // You can access the new user via result.user
      // Additional user info profile not available via:
      // result.additionalUserInfo.profile == null
      // You can check if the user is new or existing:
      // result.additionalUserInfo.isNewUser
    })
    .catch((error) => {
      // Some error occurred, you can inspect the code: error.code
      // Common errors could be invalid email and invalid or expired OTPs.
    });
}

Como concluir o login em um app para dispositivos móveis

O Firebase Authentication usa o Firebase Dynamic Links para enviar o link por e-mail a um dispositivo móvel. No aplicativo para dispositivos móveis, ele precisa estar configurado para detectar o link de entrada, analisar o link direto subjacente e então concluir o login conforme feito no fluxo da Web.

Para saber mais sobre como processar o login com o link por e-mail em um app Android, consulte este guia.

Para saber mais sobre como processar o login com o link por e-mail em um app da Apple, consulte o guia de plataformas da Apple.

Também é possível vincular este método de autenticação a um usuário atual. Por exemplo, um usuário já autenticado com outro provedor, como número de telefone, pode incluir este método de login na conta dele.

A diferença está na segunda metade da operação:

API modular da Web

import { getAuth, linkWithCredential, EmailAuthProvider } from "firebase/auth";

// Construct the email link credential from the current URL.
const credential = EmailAuthProvider.credentialWithLink(
  email, window.location.href);

// Link the credential to the current user.
const auth = getAuth();
linkWithCredential(auth.currentUser, credential)
  .then((usercred) => {
    // The provider is now successfully linked.
    // The phone user can now sign in with their phone number or email.
  })
  .catch((error) => {
    // Some error occurred.
  });

API com namespace da Web

// Construct the email link credential from the current URL.
var credential = firebase.auth.EmailAuthProvider.credentialWithLink(
  email, window.location.href);

// Link the credential to the current user.
firebase.auth().currentUser.linkWithCredential(credential)
  .then((usercred) => {
    // The provider is now successfully linked.
    // The phone user can now sign in with their phone number or email.
  })
  .catch((error) => {
    // Some error occurred.
  });

Também é possível usá-lo para reautenticar um usuário de link por e-mail antes de executar uma operação confidencial.

API modular da Web

import { getAuth, reauthenticateWithCredential, EmailAuthProvider } from "firebase/auth";

// Construct the email link credential from the current URL.
const credential = EmailAuthProvider.credentialWithLink(
  email, window.location.href);

// Re-authenticate the user with this credential.
const auth = getAuth();
reauthenticateWithCredential(auth.currentUser, credential)
  .then((usercred) => {
    // The user is now successfully re-authenticated and can execute sensitive
    // operations.
  })
  .catch((error) => {
    // Some error occurred.
  });

API com namespace da Web

// Construct the email link credential from the current URL.
var credential = firebase.auth.EmailAuthProvider.credentialWithLink(
  email, window.location.href);

// Re-authenticate the user with this credential.
firebase.auth().currentUser.reauthenticateWithCredential(credential)
  .then((usercred) => {
    // The user is now successfully re-authenticated and can execute sensitive
    // operations.
  })
  .catch((error) => {
    // Some error occurred.
  });

No entanto, este fluxo pode não ser concluído, já que ele pode terminar em um dispositivo diferente do que o usado para fazer login. Nesse caso, é possível exibir um erro ao usuário para obrigá-lo a abrir o link no mesmo dispositivo. Você pode transmitir um estado no link para fornecer informações sobre o tipo de operação e o UID do usuário.

Se você aceita logins por senhas e links de e-mail, diferencie o método de login de uma senha/link de usuário com fetchSignInMethodsForEmail. Isso é útil nos fluxos que priorizam identificadores, em que o usuário precisa indicar o e-mail e, em seguida, escolher o método de login:

API modular da Web

import { getAuth, fetchSignInMethodsForEmail, EmailAuthProvider} from "firebase/auth";

// After asking the user for their email.
const email = window.prompt('Please provide your email');

const auth = getAuth();
fetchSignInMethodsForEmail(auth, email)
  .then((signInMethods) => {
    // This returns the same array as fetchProvidersForEmail but for email
    // provider identified by 'password' string, signInMethods would contain 2
    // different strings:
    // 'emailLink' if the user previously signed in with an email/link
    // 'password' if the user has a password.
    // A user could have both.
    if (signInMethods.indexOf(EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD) != -1) {
      // User can sign in with email/password.
    }
    if (signInMethods.indexOf(EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD) != -1) {
      // User can sign in with email/link.
    }
  })
  .catch((error) => {
    // Some error occurred, you can inspect the code: error.code
  });

API com namespace da Web

// After asking the user for their email.
var email = window.prompt('Please provide your email');
firebase.auth().fetchSignInMethodsForEmail(email)
  .then((signInMethods) => {
    // This returns the same array as fetchProvidersForEmail but for email
    // provider identified by 'password' string, signInMethods would contain 2
    // different strings:
    // 'emailLink' if the user previously signed in with an email/link
    // 'password' if the user has a password.
    // A user could have both.
    if (signInMethods.indexOf(
            firebase.auth.EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD) != -1) {
      // User can sign in with email/password.
    }
    if (signInMethods.indexOf(
            firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD) != -1) {
      // User can sign in with email/link.
    }
  })
  .catch((error) => {
    // Some error occurred, you can inspect the code: error.code
  });

Conforme descrito acima, e-mail/senha e e-mail/link são considerados os mesmos firebase.auth.EmailAuthProvider (mesmo PROVIDER_ID) com diferentes métodos de login.

Próximas etapas

Depois que um usuário faz login pela primeira vez, uma nova conta de usuário é criada e vinculada às credenciais, que podem ser o número do telefone, o nome de usuário e a senha ou as informações do provedor de autenticação. Essa nova conta é armazenada como parte do projeto do Firebase e pode ser usada para identificar um usuário em todos os apps do projeto, seja qual for o método de login utilizado.

  • Nos apps, a maneira recomendada de saber o status de autenticação do seu usuário é definindo um observador no objeto Auth. É possível, então, receber as informações básicas de perfil do usuário do objeto User. Consulte Gerenciar usuários.

  • Nas Regras de segurança do Firebase Realtime Database e do Cloud Storage, é possível receber o ID exclusivo do usuário conectado da variável auth e usar esse ID para controlar quais dados uma pessoa pode acessar.

Os usuários podem fazer login no app usando vários provedores de autenticação. Basta vincular as credenciais desses provedores a uma conta de usuário.

Para desconectar um usuário, chame signOut:

API modular da Web

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

const auth = getAuth();
signOut(auth).then(() => {
  // Sign-out successful.
}).catch((error) => {
  // An error happened.
});

API com namespace da Web

firebase.auth().signOut().then(() => {
  // Sign-out successful.
}).catch((error) => {
  // An error happened.
});