Search code examples
reactjsfirebasegoogle-chrome-extensionfirebase-authenticationchrome-extension-manifest-v3

Firebase authentication not working in Chrome extension but works fine in React app


I'm building a manifest v3 Chrome Extension. The expected behavior is

  1. User clicks extension icon in toolbar
  • If user is signed in, pop-up opens, showing user's details "Hello firstName, Welcome."
    • If signed in, allow full functionality of the app.
  • If user is not signed in, a new tab opens and navigates to a settings page, which allows the user to sign in with Google.

I'm trying to use Sign in With Google with Firebase auth in my Chrome Extension. In my popup.ts file, I want to send a message to the content script to check if the user is currently logged in. If the user isn't logged in, I want to open a new tab with a button that allows a user to sign in with Google.

Here is the relevant code in my popup.ts

document.addEventListener("DOMContentLoaded", () => {

  const buttonPopup = document.getElementById("button-popup") as HTMLButtonElement;

  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    console.log("popup script");
    if (!tabs[0]) return;

    chrome.tabs.sendMessage(tabs[0].id!, { type: "GET_AUTH_STATUS" }, (response) => {
      console.log("Response: ", response)
      
      if (1 + 2 == 4 ) { //response.isLoggedIn, auth not working yet so hardcoded to false) {
        buttonPopup.style.display = "block";
      } else {
        chrome.tabs.create({
          url: chrome.runtime.getURL("build/index.html"),
        });
        window.close();
      }
    });
  });

This should open build/index.html which is a "Settings Page" that I built separately as a react app. When I build the react app, I output the built files into the Chrome Extension's dist/build/ directory.

This is the code for the App.tsx of the Settings Page:

import React from "react";
import { auth } from "./firebase";
import { GoogleAuthProvider, signInWithPopup } from "firebase/auth";
import { AuthProvider, useAuth } from "./AuthProvider";

const SignInButton: React.FC = () => {
  const handleSignIn = async () => {
    const provider = new GoogleAuthProvider();
    try {
      
      await signInWithPopup(auth, provider);
    } catch (error) {
      console.error("App.tsx handleSignIn error");
    }
  };

  return <button onClick={handleSignIn}>Sign in with Google</button>;
};

const SettingsPage: React.FC = () => {
  const { user, initializing } = useAuth();

  if (initializing) {
    return <div>Loading...</div>;
  }

  return (
    <div className="App">
      <header className="App-header">
        <h1>Settings Page</h1>
        {!user && <SignInButton />}
        {user && <p>Welcome, {user.displayName}!</p>}
      </header>
    </div>
  );
};

const App: React.FC = () => {
  return (
    <AuthProvider>
      <SettingsPage />
    </AuthProvider>
  );
};

export default App;

and the firebase.ts file:

import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";

const firebaseConfig = {
  apiKey: "your-api-key",
  authDomain: "your-auth-domain",
  projectId: "your-project-id",
  storageBucket: "your-storage-bucket",
  messagingSenderId: "your-messaging-sender-id",
  appId: "your-app-id"
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

export { app, auth };

The Settings Page opens correctly, the same as it does when I run the react app locally on its own.

However, when I click the Sign In With Google button, instead of popping up the Google Sign in Page, it gives me this error in the console:

load_js.ts:38 Refused to load the script
 'https://apis.google.com/js/api.js?onload=__iframefcb174294' because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' 'unsafe-eval'". 
Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

When I click the Sign In With Google button when running the react app locally, the Google Sign in Page pops up normally and I'm able to sign in successfully.

What I've tried: I don't understand the content security policy very well, but I've tried adding to the manifest file in different ways.

I've tried adding it to the top level of the manifest.json file:

"content_security_policy": {
    "script-src": "https://apis.google.com/js/api.js"
  },

and as an action:

  "action": {
    "content_security_policy": {
      "extension_pages": "script-src 'self' https://apis.google.com; object-src 'self'"
    },

I've checked the Firebase config and I have localhost:3000 and my chrome extension url in the approved urls.


Solution

  • You're trying to load external resource in MV3 extension, which will not work, regardless what you put into CSP. Accessing external resource violates Improved Security principle (more here).

    What you can do is to use chrome.identity for OAuth authentications (docs here), instead of standard Firebase authentication.

    If you don't want to change your implementation, you can try to host your page somewhere else (instead of bundling it to your extension) and navigate the user to your authentication website. You can communicate between that website by using two-way communication, for example, between your website & content script injected on that website, and content script & your extension service worker.

    More about communication methods here