Como fazer login dos usuários usando uma extensão do Chrome

Este documento mostra como usar o Identity Platform para fazer o login de usuários em uma extensão do Chrome que usa o Manifest V3.

O Identity Platform oferece vários métodos de autenticação para fazer login de usuários usando uma extensão do Chrome, alguns que exigem mais esforço de desenvolvimento do que outros.

Para usar estes métodos em uma extensão Manifest V3 do Chrome, você só precisa importá-los de firebase/auth/web-extension:

  • Login com e-mail e senha (createUserWithEmailAndPassword e signInWithEmailAndPassword)
  • Fazer login com o link de e-mail (sendSignInLinkToEmail, isSignInWithEmailLink e signInWithEmailLink)
  • Fazer login anonimamente (signInAnonymously)
  • Fazer login com um sistema de autenticação personalizado (signInWithCustomToken)
  • Processe o login do provedor de maneira independente e use signInWithCredential.

Estes métodos de login também são permitidos, mas exigem algumas ações adicionais:

  • Faça login em uma janela pop-up (signInWithPopup, linkWithPopup e reauthenticateWithPopup)
  • Faça login redirecionando para a página de login (signInWithRedirect, linkWithRedirect e reauthenticateWithRedirect)
  • Fazer login com o número de telefone usando o reCAPTCHA
  • Autenticação multifator por SMS com reCAPTCHA
  • Proteção do reCAPTCHA Enterprise

Para usar esses métodos em uma extensão Manifest V3 do Chrome, use Documentos fora da tela.

Usar o ponto de entrada firebase/auth/web-extension

Importar de firebase/auth/web-extension torna o login de usuários por uma extensão do Chrome semelhante a um app da Web.

O firebase/auth/web-extension é compatível apenas com o SDK da Web v10.8.0 ou mais recente.

import { getAuth, signInWithEmailAndPassword } from 'firebase/auth/web-extension';

const auth = getAuth();
signInWithEmailAndPassword(auth, email, password)
  .then((userCredential) => {
    // Signed in
    const user = userCredential.user;
    // ...
  })
  .catch((error) => {
    const errorCode = error.code;
    const errorMessage = error.message;
  });

Usar documentos fora da tela

Alguns métodos de autenticação, como signInWithPopup, linkWithPopup e reauthenticateWithPopup, não são diretamente compatíveis com as extensões do Chrome porque exigem que o código seja carregado de fora do pacote de extensões. A partir do Manifest V3, isso não é permitido e será bloqueado pela plataforma de extensões. Para contornar esse problema, carregue esse código em um iframe usando um documento fora da tela. No documento fora da tela, implemente o fluxo de autenticação normal e faça proxy do resultado do documento fora da tela de volta para a extensão.

Este guia usa signInWithPopup como exemplo, mas o mesmo conceito se aplica a outros métodos de autenticação.

Antes de começar

Essa técnica exige que você configure uma página da Web disponível na Web que será carregada em um iframe. Qualquer host funciona para isso, incluindo o Firebase Hosting. Crie um site com o conteúdo a seguir:

<!DOCTYPE html>
<html>
  <head>
    <title>signInWithPopup</title>
    <script src="signInWithPopup.js"></script>
  </head>
  <body><h1>signInWithPopup</h1></body>
</html>

Login federado

Se você estiver usando o login federado, como o login com o Google, Apple, SAML ou OIDC, adicione o ID da extensão do Chrome à lista de domínios autorizados:

  1. Acesse a página Configurações do Identity Platform na console do Google Cloud.

    Ir para a página "Configurações"

  2. Selecione a guia Segurança.

  3. Em Domínios autorizados, clique em Adicionar domínio.

  4. Insira o URI da sua extensão. Que precisa ser semelhante a este: chrome-extension://CHROME_EXTENSION_ID

  5. Clique em Adicionar.

No arquivo de manifesto da sua extensão do Chrome, adicione os seguintes URLs à lista de permissões content_security_policy:

  • https://apis.google.com
  • https://www.gstatic.com
  • https://www.googleapis.com
  • https://securetoken.googleapis.com

Implementar autenticação

No documento HTML, signInWithPopup.js é o código JavaScript que processa a autenticação. Há duas maneiras diferentes de implementar um método diretamente compatível com a extensão:

  • Use firebase/auth em vez de firebase/auth/web-extension. O ponto de entrada web-extension é para o código em execução na extensão. Embora esse código seja executado na extensão (em um iframe, no documento fora da tela), o contexto em que ele é executado é a Web padrão.
  • Una a lógica de autenticação em um listener postMessage para fazer proxy da solicitação e da resposta de autenticação.
import { signInWithPopup, GoogleAuthProvider, getAuth } from'firebase/auth';
import { initializeApp } from 'firebase/app';
import firebaseConfig from './firebaseConfig.js'

const app = initializeApp(firebaseConfig);
const auth = getAuth();

// This code runs inside of an iframe in the extension's offscreen document.
// This gives you a reference to the parent frame, i.e. the offscreen document.
// You will need this to assign the targetOrigin for postMessage.
const PARENT_FRAME = document.location.ancestorOrigins[0];

// This demo uses the Google auth provider, but any supported provider works.
// Make sure that you enable any provider you want to use in the Firebase Console.
// https://console.firebase.google.com/project/_/authentication/providers
const PROVIDER = new GoogleAuthProvider();

function sendResponse(result) {
  globalThis.parent.self.postMessage(JSON.stringify(result), PARENT_FRAME);
}

globalThis.addEventListener('message', function({data}) {
  if (data.initAuth) {
    // Opens the Google sign-in page in a popup, inside of an iframe in the
    // extension's offscreen document.
    // To centralize logic, all respones are forwarded to the parent frame,
    // which goes on to forward them to the extension's service worker.
    signInWithPopup(auth, PROVIDER)
      .then(sendResponse)
      .catch(sendResponse)
  }
});

Criar sua extensão do Chrome

Quando seu site estiver ativo, será possível usá-lo na sua extensão do Chrome.

  1. Adicione a permissão offscreen ao seu arquivo manifest.json:
  2.     {
          "name": "signInWithPopup Demo",
          "manifest_version" 3,
          "background": {
            "service_worker": "background.js"
          },
          "permissions": [
            "offscreen"
          ]
        }
        
  3. Criar o próprio documento fora da tela. Esse é um arquivo HTML mínimo dentro do pacote de extensão que carrega a lógica do JavaScript do documento fora da tela:
  4.     <!DOCTYPE html>
        <script src="./offscreen.js"></script>
        
  5. Inclua offscreen.js no seu pacote de extensões. Ele funciona como o proxy entre o site público configurado na etapa 1 e sua extensão.
  6.     // This URL must point to the public site
        const _URL = 'https://example.com/signInWithPopupExample';
        const iframe = document.createElement('iframe');
        iframe.src = _URL;
        document.documentElement.appendChild(iframe);
        chrome.runtime.onMessage.addListener(handleChromeMessages);
    
        function handleChromeMessages(message, sender, sendResponse) {
          // Extensions may have an number of other reasons to send messages, so you
          // should filter out any that are not meant for the offscreen document.
          if (message.target !== 'offscreen') {
            return false;
          }
    
          function handleIframeMessage({data}) {
            try {
              if (data.startsWith('!_{')) {
                // Other parts of the Firebase library send messages using postMessage.
                // You don't care about them in this context, so return early.
                return;
              }
              data = JSON.parse(data);
              self.removeEventListener('message', handleIframeMessage);
    
              sendResponse(data);
            } catch (e) {
              console.log(`json parse failed - ${e.message}`);
            }
          }
    
          globalThis.addEventListener('message', handleIframeMessage, false);
    
          // Initialize the authentication flow in the iframed document. You must set the
          // second argument (targetOrigin) of the message in order for it to be successfully
          // delivered.
          iframe.contentWindow.postMessage({"initAuth": true}, new URL(_URL).origin);
          return true;
        }
        
  7. Configure o documento fora da tela do service worker background.js.
  8.     const OFFSCREEN_DOCUMENT_PATH = '/offscreen.html';
    
        // A global promise to avoid concurrency issues
        let creatingOffscreenDocument;
    
        // Chrome only allows for a single offscreenDocument. This is a helper function
        // that returns a boolean indicating if a document is already active.
        async function hasDocument() {
          // Check all windows controlled by the service worker to see if one
          // of them is the offscreen document with the given path
          const matchedClients = await clients.matchAll();
          return matchedClients.some(
            (c) => c.url === chrome.runtime.getURL(OFFSCREEN_DOCUMENT_PATH)
          );
        }
    
        async function setupOffscreenDocument(path) {
          // If we do not have a document, we are already setup and can skip
          if (!(await hasDocument())) {
            // create offscreen document
            if (creating) {
              await creating;
            } else {
              creating = chrome.offscreen.createDocument({
                url: path,
                reasons: [
                    chrome.offscreen.Reason.DOM_SCRAPING
                ],
                justification: 'authentication'
              });
              await creating;
              creating = null;
            }
          }
        }
    
        async function closeOffscreenDocument() {
          if (!(await hasDocument())) {
            return;
          }
          await chrome.offscreen.closeDocument();
        }
    
        function getAuth() {
          return new Promise(async (resolve, reject) => {
            const auth = await chrome.runtime.sendMessage({
              type: 'firebase-auth',
              target: 'offscreen'
            });
            auth?.name !== 'FirebaseError' ? resolve(auth) : reject(auth);
          })
        }
    
        async function firebaseAuth() {
          await setupOffscreenDocument(OFFSCREEN_DOCUMENT_PATH);
    
          const auth = await getAuth()
            .then((auth) => {
              console.log('User Authenticated', auth);
              return auth;
            })
            .catch(err => {
              if (err.code === 'auth/operation-not-allowed') {
                console.error('You must enable an OAuth provider in the Firebase' +
                              ' console in order to use signInWithPopup. This sample' +
                              ' uses Google by default.');
              } else {
                console.error(err);
                return err;
              }
            })
            .finally(closeOffscreenDocument)
    
          return auth;
        }
        

    Agora, quando você chamar firebaseAuth() no service worker, ele vai criar o documento fora da tela e carregar o site em um iframe. Esse iframe será processado em segundo plano, e o Firebase passará pelo fluxo de autenticação padrão. Depois de ser resolvido ou rejeitado, o objeto de autenticação será encaminhado por proxy do seu iframe para o service worker, usando o documento fora da tela.