ตรวจสอบสิทธิ์ด้วย Firebase ในส่วนขยาย Chrome

เอกสารนี้แสดงวิธีใช้ Firebase Authentication เพื่อให้ผู้ใช้ลงชื่อเข้าใช้ส่วนขยาย Chrome ที่ใช้ Manifest V3

Firebase Authentication มีวิธีการตรวจสอบสิทธิ์หลายวิธีในการลงชื่อเข้าใช้ผู้ใช้จาก ส่วนขยาย Chrome ซึ่งบางวิธีต้องใช้ความพยายามในการพัฒนามากกว่าวิธีอื่นๆ

หากต้องการใช้วิธีต่อไปนี้ในส่วนขยาย Chrome ที่มีไฟล์ Manifest V3 คุณเพียงแค่ต้อง นำเข้าจาก firebase/auth/web-extension

  • ลงชื่อเข้าใช้ด้วยอีเมลและรหัสผ่าน (createUserWithEmailAndPassword และ signInWithEmailAndPassword)
  • ลงชื่อเข้าใช้ด้วยลิงก์ในอีเมล (sendSignInLinkToEmail, isSignInWithEmailLink และ signInWithEmailLink)
  • ลงชื่อเข้าใช้โดยไม่ระบุตัวตน (signInAnonymously)
  • ลงชื่อเข้าใช้ด้วยระบบการตรวจสอบสิทธิ์ที่กำหนดเอง (signInWithCustomToken)
  • จัดการการลงชื่อเข้าใช้ของผู้ให้บริการแยกกัน แล้วใช้ signInWithCredential

นอกจากนี้ ระบบยังรองรับวิธีการลงชื่อเข้าใช้ต่อไปนี้ด้วย แต่ต้องมีการดำเนินการเพิ่มเติม

  • ลงชื่อเข้าใช้ด้วยหน้าต่างป๊อปอัป (signInWithPopup, linkWithPopup และ reauthenticateWithPopup)
  • ลงชื่อเข้าใช้โดยเปลี่ยนเส้นทางไปยังหน้าลงชื่อเข้าใช้ (signInWithRedirect, linkWithRedirect และ reauthenticateWithRedirect)
  • ลงชื่อเข้าใช้ด้วยหมายเลขโทรศัพท์ด้วย reCAPTCHA
  • การตรวจสอบสิทธิ์แบบหลายปัจจัยทาง SMS ด้วย reCAPTCHA
  • การป้องกันด้วย reCAPTCHA Enterprise

หากต้องการใช้วิธีเหล่านี้ในส่วนขยาย Chrome ที่ใช้ไฟล์ Manifest V3 คุณต้องใช้เอกสารนอกหน้าจอ

ใช้จุดแรกเข้า firebase/auth/web-extension

การนำเข้าจาก firebase/auth/web-extension ทำให้การลงชื่อเข้าใช้ของผู้ใช้จากส่วนขยาย Chrome คล้ายกับเว็บแอป

firebase/auth/web-extension รองรับเฉพาะ SDK เว็บเวอร์ชัน v10.8.0 ขึ้นไป

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 ไม่สามารถใช้ร่วมกับส่วนขยาย Chrome ได้โดยตรง เนื่องจากต้องโหลดโค้ดจากภายนอกแพ็กเกจส่วนขยาย ตั้งแต่ Manifest V3 เป็นต้นไป ระบบจะไม่อนุญาตให้ทำเช่นนี้และแพลตฟอร์มส่วนขยายจะบล็อกการดำเนินการดังกล่าว หากต้องการหลีกเลี่ยงปัญหานี้ คุณสามารถโหลดโค้ดดังกล่าวภายใน iframe โดยใช้เอกสารนอกหน้าจอ ในเอกสารนอกหน้าจอ ให้ใช้ขั้นตอนการตรวจสอบสิทธิ์ปกติและพร็อกซี ผลลัพธ์จากเอกสารนอกหน้าจอกลับไปยังส่วนขยาย

คู่มือนี้ใช้ signInWithPopup เป็นตัวอย่าง แต่แนวคิดเดียวกันนี้ ใช้ได้กับวิธีการตรวจสอบสิทธิ์อื่นๆ

ก่อนเริ่มต้น

เทคนิคนี้กำหนดให้คุณต้องตั้งค่าหน้าเว็บที่พร้อมใช้งานบนเว็บ ซึ่งคุณจะโหลดใน iframe โฮสต์ใดก็ได้สำหรับกรณีนี้ รวมถึงโฮสติ้งของ Firebase สร้างเว็บไซต์ที่มีเนื้อหาต่อไปนี้

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

การลงชื่อเข้าใช้แบบรวมศูนย์

หากคุณใช้การลงชื่อเข้าใช้แบบรวม เช่น ลงชื่อเข้าใช้ด้วย Google, Apple, SAML หรือ OIDC คุณต้องเพิ่มรหัสส่วนขยาย Chrome ลงในรายการโดเมนที่ได้รับอนุญาตโดยทำดังนี้

  1. เปิดโปรเจ็กต์ในFirebase Console
  2. ในส่วนการตรวจสอบสิทธิ์ ให้เปิดหน้าการตั้งค่า
  3. เพิ่ม URI เช่น URI ต่อไปนี้ลงในรายการโดเมนที่ได้รับอนุญาต
    chrome-extension://CHROME_EXTENSION_ID

ในไฟล์ Manifest ของส่วนขยาย Chrome ให้ตรวจสอบว่าคุณได้เพิ่ม URL ต่อไปนี้ลงในรายการที่อนุญาตของ content_security_policy แล้ว

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

ใช้การตรวจสอบสิทธิ์

ในเอกสาร HTML ไฟล์ signInWithPopup.js คือโค้ด JavaScript ที่จัดการการตรวจสอบสิทธิ์ การติดตั้งใช้งานเมธอดที่ส่วนขยายรองรับโดยตรงทำได้ 2 วิธีดังนี้

  • ใช้ firebase/auth/web-extension ในโค้ดส่วนขยาย เช่น สคริปต์พื้นหลัง, Service Worker หรือสคริปต์ป๊อปอัป ใช้ 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)
  }
});

สร้างส่วนขยาย Chrome

หลังจากเผยแพร่เว็บไซต์แล้ว คุณจะใช้เว็บไซต์ในส่วนขยาย Chrome ได้

  1. เพิ่มสิทธิ์ offscreen ลงในไฟล์ manifest.json ดังนี้
  2.     {
          "name": "signInWithPopup Demo",
          "manifest_version" 3,
          "background": {
            "service_worker": "background.js"
          },
          "permissions": [
            "offscreen"
          ]
        }
        
  3. สร้างเอกสารนอกหน้าจอ นี่คือไฟล์ HTML ขั้นต่ำภายในแพ็กเกจส่วนขยายที่ โหลดตรรกะของ JavaScript ของเอกสารนอกหน้าจอ
  4.     <!DOCTYPE html>
        <script src="./offscreen.js"></script>
        
  5. รวม offscreen.js ไว้ในแพ็กเกจส่วนขยาย โดยจะทำหน้าที่เป็นพร็อกซีระหว่าง เว็บไซต์สาธารณะที่ตั้งค่าไว้ในขั้นตอนที่ 1 กับส่วนขยายของคุณ
  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. ตั้งค่าเอกสารนอกหน้าจอจาก Service Worker ของ 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() ภายใน Service Worker ระบบจะสร้าง เอกสารนอกหน้าจอและโหลดเว็บไซต์ใน iframe iframe นั้นจะประมวลผลในเบื้องหลัง และ Firebase จะดำเนินการตามขั้นตอนการตรวจสอบสิทธิ์มาตรฐาน เมื่อแก้ไขหรือปฏิเสธแล้ว ระบบจะส่งต่อออบเจ็กต์การตรวจสอบสิทธิ์ จาก iframe ไปยัง Service Worker โดยใช้เอกสารนอกหน้าจอ