Authentifizieren Sie sich mit Firebase in einer Chrome-Erweiterung

In diesem Dokument erfahren Sie, wie Sie mithilfe der Firebase-Authentifizierung Benutzer bei einer Chrome-Erweiterung anmelden, die Manifest V3 verwendet.

Firebase Authentication bietet mehrere Authentifizierungsmethoden zum Anmelden von Benutzern über eine Chrome-Erweiterung, wobei einige mehr Entwicklungsaufwand erfordern als andere.

Um die folgenden Methoden in einer Manifest V3 Chrome-Erweiterung zu verwenden, müssen Sie sie nur aus firebase/auth/web-extension importieren :

  • Melden Sie sich mit E-Mail und Passwort an ( createUserWithEmailAndPassword und signInWithEmailAndPassword )
  • Melden Sie sich mit einem E-Mail-Link an ( sendSignInLinkToEmail , isSignInWithEmailLink und signInWithEmailLink )
  • Anonym anmelden ( signInAnonymously )
  • Melden Sie sich mit einem benutzerdefinierten Authentifizierungssystem an ( signInWithCustomToken )
  • Behandeln Sie die Anbieteranmeldung unabhängig und verwenden Sie dann signInWithCredential

Die folgenden Anmeldemethoden werden ebenfalls unterstützt, erfordern jedoch etwas zusätzlichen Aufwand:

  • Melden Sie sich mit einem Popup-Fenster an ( signInWithPopup , linkWithPopup und reauthenticateWithPopup )
  • Melden Sie sich an, indem Sie zur Anmeldeseite weiterleiten ( signInWithRedirect , linkWithRedirect und reauthenticateWithRedirect ).
  • Melden Sie sich mit der Telefonnummer mit reCAPTCHA an
  • SMS-Multifaktor-Authentifizierung mit reCAPTCHA
  • reCAPTCHA Enterprise-Schutz

Um diese Methoden in einer Manifest V3 Chrome-Erweiterung zu verwenden, müssen Sie Offscreen Documents verwenden.

Verwenden Sie den Einstiegspunkt firebase/auth/web-extension

Durch den Import aus firebase/auth/web-extension erfolgt die Anmeldung von Benutzern über eine Chrome-Erweiterung ähnlich wie bei einer Web-App.

firebase/auth/web-extension wird nur auf den Web SDK-Versionen v10.8.0 und höher unterstützt.

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

Verwenden Sie Offscreen-Dokumente

Einige Authentifizierungsmethoden wie signInWithPopup , linkWithPopup und reauthenticateWithPopup sind nicht direkt mit Chrome-Erweiterungen kompatibel, da sie das Laden von Code von außerhalb des Erweiterungspakets erfordern. Ab Manifest V3 ist dies nicht zulässig und wird von der Erweiterungsplattform blockiert. Um dies zu umgehen, können Sie diesen Code mithilfe eines Offscreen-Dokuments in einen Iframe laden. Implementieren Sie im Offscreen-Dokument den normalen Authentifizierungsablauf und leiten Sie das Ergebnis aus dem Offscreen-Dokument zurück an die Erweiterung.

In dieser Anleitung wird signInWithPopup als Beispiel verwendet, das gleiche Konzept gilt jedoch auch für andere Authentifizierungsmethoden.

Bevor Sie beginnen

Für diese Technik müssen Sie eine im Web verfügbare Webseite einrichten, die Sie in einen Iframe laden. Hierfür eignet sich jeder Host, einschließlich Firebase Hosting . Erstellen Sie eine Website mit folgendem Inhalt:

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

Verbundanmeldung

Wenn Sie die Verbundanmeldung verwenden, z. B. die Anmeldung mit Google, Apple, SAML oder OIDC, müssen Sie Ihre Chrome-Erweiterungs-ID zur Liste der autorisierten Domänen hinzufügen:

  1. Öffnen Sie Ihr Projekt in der Firebase-Konsole .
  2. Öffnen Sie im Abschnitt Authentifizierung die Seite Einstellungen .
  3. Fügen Sie der Liste der autorisierten Domänen einen URI wie den folgenden hinzu:
    chrome-extension://CHROME_EXTENSION_ID

Stellen Sie in der Manifestdatei Ihrer Chrome-Erweiterung sicher, dass Sie die folgenden URLs zur Zulassungsliste content_security_policy hinzufügen:

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

Implementieren Sie die Authentifizierung

In Ihrem HTML-Dokument ist signInWithPopup.js der JavaScript-Code, der die Authentifizierung übernimmt. Es gibt zwei verschiedene Möglichkeiten, eine Methode zu implementieren, die direkt in der Erweiterung unterstützt wird:

  • Verwenden Sie firebase/auth anstelle von firebase/auth/web-extension . Der Einstiegspunkt der web-extension dient für Code, der innerhalb der Erweiterung ausgeführt wird. Während dieser Code letztendlich in der Erweiterung ausgeführt wird (in einem Iframe, in Ihrem Offscreen-Dokument), ist der Kontext, in dem er ausgeführt wird, das Standard-Web.
  • Binden Sie die Authentifizierungslogik in einen postMessage Listener ein, um die Authentifizierungsanfrage und -antwort weiterzuleiten.
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)
  }
});

Erstellen Sie Ihre Chrome-Erweiterung

Nachdem Ihre Website online ist, können Sie sie in Ihrer Chrome-Erweiterung verwenden.

  1. Fügen Sie die offscreen Berechtigung zu Ihrer manifest.json-Datei hinzu:
  2.     {
          "name": "signInWithPopup Demo",
          "manifest_version" 3,
          "background": {
            "service_worker": "background.js"
          },
          "permissions": [
            "offscreen"
          ]
        }
        
  3. Erstellen Sie das Offscreen-Dokument selbst. Dies ist eine minimale HTML-Datei in Ihrem Erweiterungspaket, die die Logik Ihres Offscreen-Dokument-JavaScripts lädt:
  4.     <!DOCTYPE html>
        <script src="./offscreen.js"></script>
        
  5. Fügen Sie offscreen.js in Ihr Erweiterungspaket ein. Es fungiert als Proxy zwischen der in Schritt 1 eingerichteten öffentlichen Website und Ihrer Erweiterung.
  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. Richten Sie das Offscreen-Dokument über Ihren Background.js-Servicemitarbeiter ein.
  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;
        }
        

    Wenn Sie nun firebaseAuth() in Ihrem Service Worker aufrufen, wird das Offscreen-Dokument erstellt und die Site in einen Iframe geladen. Dieser Iframe wird im Hintergrund verarbeitet und Firebase durchläuft den Standardauthentifizierungsablauf. Sobald das Problem gelöst oder abgelehnt wurde, wird das Authentifizierungsobjekt mithilfe des Offscreen-Dokuments von Ihrem Iframe an Ihren Servicemitarbeiter weitergeleitet.