Accesso degli utenti da un'estensione di Chrome

Questo documento mostra come utilizzare Identity Platform per far accedere gli utenti a un'estensione di Chrome che utilizza Manifest V3.

Identity Platform fornisce più metodi di autenticazione per consentire agli utenti di accedere da un'estensione di Chrome, alcuni dei quali richiedono uno sforzo di sviluppo maggiore rispetto ad altri.

Per utilizzare i seguenti metodi in un'estensione di Chrome Manifest V3, è sufficiente importali da firebase/auth/web-extension:

  • Accedi con email e password (createUserWithEmailAndPassword e signInWithEmailAndPassword)
  • Accedi con il link email (sendSignInLinkToEmail, isSignInWithEmailLink e signInWithEmailLink)
  • Accedi in modo anonimo (signInAnonymously)
  • Accedi con un sistema di autenticazione personalizzato (signInWithCustomToken)
  • Gestisci l'accesso al provider in modo indipendente, quindi utilizza signInWithCredential

Sono supportati anche i seguenti metodi di accesso, ma richiedono alcune operazioni aggiuntive:

  • Accedi con una finestra popup (signInWithPopup, linkWithPopup e reauthenticateWithPopup)
  • Accedi reindirizzando alla pagina di accesso (signInWithRedirect, linkWithRedirect e reauthenticateWithRedirect)
  • Accedere con il numero di telefono con reCAPTCHA
  • Autenticazione a più fattori tramite SMS con reCAPTCHA
  • Protezione reCAPTCHA Enterprise

Per utilizzare questi metodi in un'estensione di Chrome Manifest V3, devi utilizzare Documenti fuori schermo.

Utilizza il punto di contatto firebase/auth/web-extension

L'importazione da firebase/auth/web-extension consente agli utenti di accedere da un'estensione di Chrome in modo simile a un'app web.

firebase/auth/web-extension è supportato solo nelle versioni dell'SDK web 10.8.0 e successive.

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

Usa documenti fuori schermo

Alcuni metodi di autenticazione, come signInWithPopup, linkWithPopup e reauthenticateWithPopup non sono direttamente compatibili con le estensioni di Chrome, perché richiedono che il codice venga caricato dall'esterno del pacchetto dell'estensione. A partire da Manifest V3, questa operazione non è consentita e verrà bloccata dal di grandi dimensioni. Per aggirare il problema, puoi caricare il codice all'interno un iframe utilizzando un documento fuori schermo. Nel documento fuori schermo, implementa il normale flusso di autenticazione e esegui il proxy per il risultato dal documento fuori schermo all'estensione.

Questa guida utilizza signInWithPopup come esempio, ma lo stesso concetto si applica agli altri metodi di autenticazione.

Prima di iniziare

Questa tecnica richiede la configurazione di una pagina web disponibile sul web, che verrà caricata in un iframe. Puoi utilizzare qualsiasi host, incluso Firebase Hosting. Crea un sito web con i seguenti contenuti:

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

Accesso federato

Se utilizzi l'accesso federato, ad esempio Accedi con Google, Apple, SAML o OIDC, devi aggiungere l'ID estensione di Chrome all'elenco di domini autorizzati domini:

  1. Vai alla pagina Impostazioni di Identity Platform nella nella console Google Cloud.

    Vai alla pagina Impostazioni

  2. Seleziona la scheda Sicurezza.

  3. In Authorized Domains (Domini autorizzati), fai clic su Add Domain (Aggiungi dominio).

  4. Inserisci l'URI dell'estensione. Dovrebbe avere un aspetto simile a questo: chrome-extension://CHROME_EXTENSION_ID.

  5. Fai clic su Aggiungi.

Nel file manifest dell'estensione di Chrome, assicurati di aggiungere quanto segue URL per la lista consentita content_security_policy:

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

Implementare l'autenticazione

Nel documento HTML, signInWithPopup.js è il codice JavaScript che gestisce l'autenticazione. Esistono due modi diversi per implementare un metodo supportato direttamente nell'estensione:

  • Utilizza firebase/auth anziché firebase/auth/web-extension. Il punto di accesso web-extension è per il codice eseguito all'interno dell'estensione. Anche se questo codice viene eseguito nell'estensione (in un iframe o nel documento fuori schermo), il contesto in cui viene eseguito è il web standard.
  • Aggrega la logica di autenticazione in un listener postMessage per inviare tramite proxy la richiesta e la risposta di autenticazione.
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)
  }
});

Creare l'estensione di Chrome

Una volta pubblicato il tuo sito web, puoi utilizzarlo nell'estensione di Chrome.

  1. Aggiungi l'autorizzazione offscreen al file manifest.json:
  2.     {
          "name": "signInWithPopup Demo",
          "manifest_version" 3,
          "background": {
            "service_worker": "background.js"
          },
          "permissions": [
            "offscreen"
          ]
        }
        
  3. Creare il documento fuori schermo. Si tratta di un file HTML minimo all'interno del pacchetto dell'estensione che carica la logica del codice JavaScript del documento offscreen:
  4.     <!DOCTYPE html>
        <script src="./offscreen.js"></script>
        
  5. Includi offscreen.js nel pacchetto dell'estensione. Agisce da proxy tra il sito web pubblico configurato nel passaggio 1 e la tua estensione.
  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. Configura il documento offscreen dal tuo 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;
        }
        

    Ora, quando chiami firebaseAuth() all'interno del tuo service worker, viene creato il documento offscreen e il sito viene caricato in un iframe. L'iframe verrà elaborato in background e Firebase eseguirà il flusso di autenticazione standard. Dopo essere stato risolto o rifiutato, l'oggetto di autenticazione verrà inviato tramite proxy dal tuo iframe al tuo service worker, usando documento.