با Firebase در یک برنامه افزودنی Chrome احراز هویت

این سند به شما نشان می‌دهد که چگونه از Firebase Authentication برای ورود کاربران به یک افزونه کروم که از Manifest V3 استفاده می‌کند، استفاده کنید.

Firebase Authentication روش‌های احراز هویت متعددی را برای ورود کاربران از طریق افزونه کروم ارائه می‌دهد که برخی از آنها به تلاش توسعه بیشتری نسبت به سایرین نیاز دارند.

برای استفاده از متدهای زیر در افزونه کروم Manifest V3، کافیست آنها را از firebase/auth/web-extension وارد کنید :

  • با ایمیل و رمز عبور وارد شوید ( createUserWithEmailAndPassword و signInWithEmailAndPassword )
  • ورود با لینک ایمیل ( sendSignInLinkToEmail ، isSignInWithEmailLink و signInWithEmailLink )
  • ورود ناشناس ( signInAnonymously )
  • ورود با یک سیستم احراز هویت سفارشی ( signInWithCustomToken )
  • ورود به سیستم ارائه دهنده را به طور مستقل مدیریت کنید و سپس از signInWithCredential استفاده کنید

روش‌های ورود به سیستم زیر نیز پشتیبانی می‌شوند اما نیاز به کار بیشتری دارند:

  • ورود به سیستم با یک پنجره پاپ‌آپ ( signInWithPopup ، linkWithPopup و reauthenticateWithPopup )
  • با هدایت به صفحه ورود ( signInWithRedirect ، linkWithRedirect و reauthenticateWithRedirect ) وارد سیستم شوید.
  • ورود با شماره تلفن و reCAPTCHA
  • احراز هویت چند عاملی پیامکی با reCAPTCHA
  • محافظت سازمانی reCAPTCHA

برای استفاده از این متدها در افزونه‌ی کروم Manifest V3، باید از Offscreen Documents استفاده کنید.

از نقطه ورود firebase/auth/web-extension استفاده کنید

وارد کردن از firebase/auth/web-extension باعث می‌شود ورود کاربران از طریق افزونه کروم شبیه به یک برنامه وب شود.

firebase/auth/web-extension فقط در نسخه‌های Web SDK نسخه ۱۰.۸.۰ و بالاتر پشتیبانی می‌شود.

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

استفاده از اسناد خارج از صفحه

برخی از روش‌های احراز هویت، مانند signInWithPopup ، linkWithPopup و reauthenticateWithPopup ، مستقیماً با افزونه‌های کروم سازگار نیستند، زیرا نیاز به بارگذاری کد از خارج از بسته افزونه دارند. از Manifest V3 به بعد، این کار مجاز نیست و توسط پلتفرم افزونه مسدود می‌شود. برای حل این مشکل، می‌توانید آن کد را در یک iframe با استفاده از یک سند خارج از صفحه بارگذاری کنید. در سند خارج از صفحه، جریان احراز هویت عادی را پیاده‌سازی کنید و نتیجه را از سند خارج از صفحه به افزونه منتقل کنید.

این راهنما از signInWithPopup به عنوان مثال استفاده می‌کند، اما همین مفهوم در مورد سایر روش‌های احراز هویت نیز صدق می‌کند.

قبل از اینکه شروع کنی

این تکنیک مستلزم آن است که شما یک صفحه وب که در وب موجود است را تنظیم کنید، که آن را در یک iframe بارگذاری خواهید کرد. هر میزبان برای این کار مناسب است، از جمله Firebase Hosting . یک وب‌سایت با محتوای زیر ایجاد کنید:

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

ورود به سیستم فدرال

اگر از ورود به سیستم فدرال، مانند ورود با گوگل، اپل، SAML یا OIDC استفاده می‌کنید، باید شناسه افزونه Chrome خود را به فهرست دامنه‌های مجاز اضافه کنید:

  1. پروژه خود را در کنسول Firebase باز کنید.
  2. در بخش احراز هویت ، صفحه تنظیمات را باز کنید.
  3. یک URI مانند زیر به لیست دامنه‌های مجاز اضافه کنید:
    chrome-extension://CHROME_EXTENSION_ID

در فایل مانیفست افزونه کروم خود، مطمئن شوید که URL های زیر را به لیست مجاز content_security_policy اضافه کرده‌اید:

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

پیاده‌سازی احراز هویت

در سند HTML شما، signInWithPopup.js کد جاوا اسکریپتی است که احراز هویت را مدیریت می‌کند. دو روش مختلف برای پیاده‌سازی روشی که مستقیماً در افزونه پشتیبانی می‌شود وجود دارد:

  • از firebase/auth/web-extension در کد افزونه خود مانند اسکریپت‌های پس‌زمینه، سرویس ورکرها یا اسکریپت‌های پاپ‌آپ استفاده کنید. firebase/auth فقط در داخل iframe خارج از صفحه خود استفاده کنید، زیرا آن iframe در یک زمینه استاندارد صفحه وب اجرا می‌شود.
  • منطق احراز هویت را در یک شنونده postMessage قرار دهید تا درخواست و پاسخ احراز هویت را پروکسی کنید.
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)
  }
});

افزونه کروم خود را بسازید

بعد از اینکه وب‌سایت شما راه‌اندازی شد، می‌توانید از آن در افزونه کروم خود استفاده کنید.

  1. مجوز offscreen را به فایل manifest.json خود اضافه کنید:
  2.     {
          "name": "signInWithPopup Demo",
          "manifest_version" 3,
          "background": {
            "service_worker": "background.js"
          },
          "permissions": [
            "offscreen"
          ]
        }
        
  3. خود سند خارج از صفحه را ایجاد کنید. این یک فایل HTML مینیمال درون بسته افزونه شماست که منطق جاوا اسکریپت سند خارج از صفحه شما را بارگذاری می‌کند:
  4.     <!DOCTYPE html>
        <script src="./offscreen.js"></script>
        
  5. offscreen.js در بسته افزونه خود قرار دهید. این فایل به عنوان پروکسی بین وب‌سایت عمومی راه‌اندازی شده در مرحله ۱ و افزونه شما عمل می‌کند.
  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. سند خارج از صفحه را از سرویس ورکر 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;
        }
        

    حالا، وقتی تابع firebaseAuth() در سرویس ورکر خود فراخوانی می‌کنید، سند خارج از صفحه را ایجاد کرده و سایت را در یک iframe بارگذاری می‌کند. آن iframe در پس‌زمینه پردازش می‌شود و Firebase جریان احراز هویت استاندارد را طی می‌کند. پس از اینکه حل یا رد شد، شیء احراز هویت با استفاده از سند خارج از صفحه از iframe شما به سرویس ورکر شما پروکسی می‌شود.