En este documento se explica cómo usar Identity Platform para que los usuarios inicien sesión en una extensión de Chrome que use Manifest V3.
Identity Platform ofrece varios métodos de autenticación para iniciar la sesión de los usuarios desde una extensión de Chrome. Algunos requieren más esfuerzo de desarrollo que otros.
Para usar los siguientes métodos en una extensión de Chrome con Manifest V3, solo tienes que importarlos desde firebase/auth/web-extension
:
- Iniciar sesión con correo electrónico y contraseña (
createUserWithEmailAndPassword
ysignInWithEmailAndPassword
) - Iniciar sesión con un enlace de correo (
sendSignInLinkToEmail
,isSignInWithEmailLink
ysignInWithEmailLink
) - Iniciar sesión de forma anónima (
signInAnonymously
) - Iniciar sesión con un sistema de autenticación personalizado (
signInWithCustomToken
) - Gestionar el inicio de sesión del proveedor de forma independiente y, a continuación, usar
signInWithCredential
También se admiten los siguientes métodos de inicio de sesión, pero requieren un trabajo adicional:
- Iniciar sesión con una ventana emergente (
signInWithPopup
,linkWithPopup
yreauthenticateWithPopup
) - Iniciar sesión redirigiendo a la página de inicio de sesión (
signInWithRedirect
,linkWithRedirect
yreauthenticateWithRedirect
) - Iniciar sesión con un número de teléfono con reCAPTCHA
- Autenticación multifactor por SMS con reCAPTCHA
- Protección de reCAPTCHA Enterprise
Para usar estos métodos en una extensión de Chrome con Manifest V3, debes usar documentos fuera de pantalla.
Usar el punto de entrada firebase/auth/web-extension
Importar desde firebase/auth/web-extension
hace que el inicio de sesión de usuarios desde una extensión de Chrome sea similar al de una aplicación web.
firebase/auth/web-extension solo se admite en las versiones 10.8.0 y posteriores del SDK 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; });
Usar documentos fuera de pantalla
Algunos métodos de autenticación, como signInWithPopup
, linkWithPopup
y reauthenticateWithPopup
, no son directamente compatibles con las extensiones de Chrome porque requieren que se cargue código desde fuera del paquete de la extensión.
En Manifest V3, esto no está permitido y la plataforma de extensiones lo bloqueará. Para evitarlo, puedes cargar ese código en un iframe mediante un documento fuera de pantalla.
En el documento fuera de pantalla, implementa el flujo de autenticación normal y envía el resultado del documento fuera de pantalla a la extensión.
En esta guía se usa signInWithPopup
como ejemplo, pero el mismo concepto se aplica a otros métodos de autenticación.
Antes de empezar
Para usar esta técnica, debes configurar una página web que esté disponible en Internet y que cargarás en un iframe. Puedes usar cualquier host, incluido Firebase Hosting. Crea un sitio web con el siguiente contenido:
<!DOCTYPE html> <html> <head> <title>signInWithPopup</title> <script src="signInWithPopup.js"></script> </head> <body><h1>signInWithPopup</h1></body> </html>
Inicio de sesión federado
Si usas un inicio de sesión federado, como el inicio de sesión con Google, Apple, SAML u OIDC, debes añadir el ID de tu extensión de Chrome a la lista de dominios autorizados:
Ve a la página Configuración de Identity Platform en laGoogle Cloud consola.
Selecciona la pestaña Seguridad.
En Dominios autorizados, haz clic en Añadir dominio.
Introduce el URI de tu extensión. Debería ser similar a esta:
chrome-extension://CHROME_EXTENSION_ID
.Haz clic en Añadir.
En el archivo de manifiesto de tu extensión de Chrome, añade las siguientes URLs a la content_security_policy
lista de permitidas:
https://apis.google.com
https://www.gstatic.com
https://www.googleapis.com
https://securetoken.googleapis.com
Implementar la autenticación
En tu documento HTML, signInWithPopup.js es el código JavaScript que gestiona la autenticación. Hay dos formas de implementar un método que se admite directamente en la extensión:
- Usa
firebase/auth
en lugar defirebase/auth/web-extension
. El punto de entradaweb-extension
es para el código que se ejecuta en la extensión. Aunque este código se ejecuta en la extensión (en un iframe, en tu documento fuera de pantalla), el contexto en el que se ejecuta es la Web estándar. - Encapsula la lógica de autenticación en un listener
postMessage
para proxy la solicitud y la respuesta de autenticación.
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) } });
Crea tu extensión de Chrome
Una vez que tu sitio web esté activo, podrás usarlo en tu extensión de Chrome.
- Añade el permiso
offscreen
a tu archivo manifest.json: - Crea el documento fuera de pantalla. Se trata de un archivo HTML mínimo incluido en el paquete de tu extensión que carga la lógica de tu archivo JavaScript de documento fuera de pantalla:
- Incluye
offscreen.js
en el paquete de la extensión. Actúa como proxy entre el sitio web público configurado en el paso 1 y tu extensión. - Configura el documento fuera de pantalla desde el service worker background.js.
{ "name": "signInWithPopup Demo", "manifest_version" 3, "background": { "service_worker": "background.js" }, "permissions": [ "offscreen" ] }
<!DOCTYPE html> <script src="./offscreen.js"></script>
// 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; }
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; }
Ahora, cuando llames a firebaseAuth()
en tu service worker, se creará el documento fuera de pantalla y se cargará el sitio en un iframe. Ese iframe se procesará en segundo plano y Firebase seguirá el flujo de autenticación estándar. Una vez que se haya resuelto o rechazado, el objeto de autenticación se proxyizará desde tu iframe a tu service worker mediante el documento fuera de pantalla.