Início de sessão de utilizadores a partir de uma extensão do Chrome

Este documento mostra como usar a Identity Platform para permitir que os utilizadores iniciem sessão numa extensão do Chrome que usa o Manifest V3.

O Identity Platform oferece vários métodos de autenticação para iniciar sessão em utilizadores a partir de uma extensão do Chrome. Alguns requerem mais esforço de desenvolvimento do que outros.

Para usar os seguintes métodos numa extensão do Chrome do Manifest V3, só precisa de os importar de firebase/auth/web-extension:

  • Inicie sessão com email e palavra-passe (createUserWithEmailAndPassword e signInWithEmailAndPassword)
  • Inicie sessão com um link por email (sendSignInLinkToEmail, isSignInWithEmailLink e signInWithEmailLink)
  • Inicie sessão anonimamente (signInAnonymously)
  • Inicie sessão com um sistema de autenticação personalizado (signInWithCustomToken)
  • Processar o início de sessão do fornecedor de forma independente e, em seguida, usar signInWithCredential

Os seguintes métodos de início de sessão também são suportados, mas requerem algum trabalho adicional:

  • Inicie sessão com uma janela de pop-up (signInWithPopup, linkWithPopup e reauthenticateWithPopup)
  • Inicie sessão através do redirecionamento para a página de início de sessão (signInWithRedirect, linkWithRedirect e reauthenticateWithRedirect)
  • Inicie sessão com o número de telefone com o reCAPTCHA
  • Autenticação multifator por SMS com reCAPTCHA
  • Proteção do reCAPTCHA Enterprise

Para usar estes métodos numa extensão do Chrome com o Manifest V3, tem de usar os documentos fora do ecrã.

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

A importação a partir de firebase/auth/web-extension faz com que o início de sessão dos utilizadores a partir de uma extensão do Chrome seja semelhante ao de uma app Web.

firebase/auth/web-extension só é suportado nas versões v10.8.0 e superiores do SDK para a Web.

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;
  });

Use documentos fora do ecrã

Alguns métodos de autenticação, como signInWithPopup, linkWithPopup e reauthenticateWithPopup, não são diretamente compatíveis com extensões do Chrome, porque requerem que o código seja carregado a partir de fora do pacote de extensão. No Manifest V3, isto não é permitido e é bloqueado pela plataforma de extensões. Para contornar esta situação, pode carregar esse código num iFrame através de um documento fora do ecrã. No documento fora do ecrã, implemente o fluxo de autenticação normal e encaminhe o resultado do documento fora do ecrã de volta para a extensão.

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

Antes de começar

Esta técnica requer que configure uma página Web disponível na Web que vai carregar num iFrame. Qualquer anfitrião funciona para este fim, incluindo o Firebase Hosting. Crie um Website com o seguinte conteúdo:

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

Início de sessão federado

Se estiver a usar o início de sessão federado, como o início de sessão com o Google, a Apple, o SAML ou o OIDC, tem de adicionar o ID da extensão do Chrome à lista de domínios autorizados:

  1. Aceda à página Definições do Identity Platform na Google Cloud consola.

    Aceda à página Definições

  2. Selecione o separador Segurança.

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

  4. Introduza o URI da extensão. Deve ter um aspeto semelhante a chrome-extension://CHROME_EXTENSION_ID.

  5. Clique em Adicionar.

No ficheiro de manifesto da extensão do Chrome, certifique-se de que adiciona 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

Implemente a autenticação

No seu documento HTML, signInWithPopup.js é o código JavaScript que processa a autenticação. Existem duas formas diferentes de implementar um método que é diretamente suportado na extensão:

  • Use firebase/auth/web-extension no código da extensão, como scripts em segundo plano, trabalhadores de serviço ou scripts de pop-up. Use firebase/auth apenas no iFrame fora do ecrã, porque esse iFrame é executado num contexto de página Web padrão.
  • Encapsule a lógica de autenticação num ouvinte postMessage para encaminhar o pedido e a 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)
  }
});

Crie a sua extensão do Chrome

Depois de o seu Website estar ativo, pode usá-lo na extensão do Chrome.

  1. Adicione a autorização offscreen ao ficheiro manifest.json:
  2.     {
          "name": "signInWithPopup Demo",
          "manifest_version" 3,
          "background": {
            "service_worker": "background.js"
          },
          "permissions": [
            "offscreen"
          ]
        }
        
  3. Crie o próprio documento fora do ecrã. Este é um ficheiro HTML mínimo no pacote da extensão que carrega a lógica do JavaScript do documento fora do ecrã:
  4.     <!DOCTYPE html>
        <script src="./offscreen.js"></script>
        
  5. Inclua offscreen.js no pacote de extensão. Funciona como o proxy entre o Website público configurado no passo 1 e a 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 do ecrã a partir do service worker background.js.
  8.     import { getAuth } from 'firebase/auth/web-extension';
    
        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 chama firebaseAuth() no service worker, cria o documento fora do ecrã e carrega o site num iFrame. Esse iFrame é processado em segundo plano e o Firebase passa pelo fluxo de autenticação padrão. Assim que for resolvida ou rejeitada, o objeto de autenticação é encaminhado do iframe para o service worker através do documento fora do ecrã.