Este documento mostra como usar o Firebase Authentication para fazer o login de usuários em uma extensão do Chrome que usa o Manifest V3.
O Firebase Authentication 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
esignInWithEmailAndPassword
) - Fazer login com o link de e-mail (
sendSignInLinkToEmail
,isSignInWithEmailLink
esignInWithEmailLink
) - 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
ereauthenticateWithPopup
) - Faça login redirecionando para a página de login (
signInWithRedirect
,linkWithRedirect
ereauthenticateWithRedirect
) - 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:
- Abra seu projeto no Console do Firebase.
- Na seção Autenticação, abra a página Configurações.
- Adicione um URI como o seguinte à lista de domínios autorizados:
chrome-extension://CHROME_EXTENSION_ID
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 defirebase/auth/web-extension
. O ponto de entradaweb-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.
- Adicione a permissão
offscreen
ao seu arquivo manifest.json: - 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:
- 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. - Configure o documento fora da tela do 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; }
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.