Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Persist auth state Firebase + Chrome Extension

I am trying to set up authentication in my Chrome Extension. I simply want users to be able to click the extension icon, log in with email + password and then be able to use the different components of the extension. If the extension popup is closed, they shouldn't have to login again.

I have been scrupulously following the documentation here, here and the Chrome Extension specific documentation here with my very limited dev experience.

Here is my manifest.json.

{
  "name": "Extension",
  "version": "0.1",
  "description": "",
  "permissions": [
    "tabs",
    "activeTab",
    "storage"
  ],
  "content_security_policy": "script-src 'self' https://www.gstatic.com/ https://*.firebaseio.com https://*.firebase.com https://www.googleapis.com; object-src 'self'",
  "background": {
    "page":"background.html",
    "persistent": true
  },
  "browser_action": {
    "default_popup": "popup.html",
    "default_icon": {}
  },
  "manifest_version": 2
}

My background.html and background.js are rigorously identical to the last example I linked to above except off course I replaced the Firebase config with my own.

My popup.html is:

<!DOCTYPE html>
<html lang="en" dir="ltr">

  <head>
    <meta charset="utf-8">
    <title></title>
    <script src="https://www.gstatic.com/firebasejs/5.10.0/firebase.js"></script>
    <script src="firebase/initialize.js" charset="utf-8"></script>
    <script src="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.js"></script>
    <link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.css"/>
    <link type="text/css" rel="stylesheet" href="styles/popup.css" />
  </head>

  <body>
    <h1>Extension</h1>
    <div id="firebaseui-auth-container"></div>
    <!-- Firebase App (the core Firebase SDK) is always required and must be listed first -->
    <script src="src/popup.js"></script>
  </body>

</html>

Where initialize.js is simply the Firebase initialization script and my popup.js is:

//Initizalier FirebaseUI instance
var ui = new firebaseui.auth.AuthUI(firebase.auth());
//Define sign-in methods
var uiConfig = {
  callbacks: {
    //Callbacl if sign up successful
    signInSuccessWithAuthResult: function(authResult) {
      // User successfully signed in.
      // Return type determines whether we continue the redirect automatically
      // or whether we leave that to developer to handle.

      //Script to replace popup content with my Extension UI

      return false;
    },
  },
  signInOptions: [
    {
      provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
      requireDisplayName: false
    }
  ],
};

//Initialize UI
ui.start('#firebaseui-auth-container', uiConfig);

Using this, I can say that login works each time I click on the extension icon, but if I close and reopen the popup, I have to login again. I have been asking about this pretty much every where I could. I also tried to ask the answer to this stackoverflow question to my background.js script but to no avail.

If I am not completely lost, I think I have an idea of why it is this way. If I'm not mistaken, the browser action popup overrides what the background page / script are doing. However I am not sure what I should modify. I have also looked at this about authenticating Firebase with Chrome Extensions but I can't make sense of how it fits into my work.

At this point I have no idea what to do next.

like image 986
Experience111 Avatar asked Apr 22 '19 21:04

Experience111


1 Answers

I have found a solution to my problem. The answer from @cloop in the question I linked to is correct but for the sake of people that will come across this question later I will explain in my own way. I hope that it will be more beginner friendly as I am not a developer myself.

Note that this is merely my solution to have a MVP, there is very likely a better way to go about this.

First of all, do not use the Firebase UI library as this will not be compatible with Chrome Extensions. I will assume that you have gone through the Chrome Extension tutorial as well as the basic of the Firebase Authentication documentation.

Now we have to discard everything we have so far and start over with an empty extension. We will set up email + password authentication for it.

First we create the manifest:

{
  "name": "Test extension",
  "version": "0.1",
  "description": "An extension to test Firebase auth",
  "permissions": [/*Insert necessary permissions*/],
  "content_security_policy": "script-src 'self' https://www.gstatic.com/ https://*.firebaseio.com https://*.firebase.com https://www.googleapis.com; object-src 'self'",
  "background": {
    "page":"background.html",
    "persistent": true
  },
  "browser_action": {
    "default_popup": "popup.html",
  },
  "manifest_version": 2
}

background.html is the background page that is linked to a background.js script. This is because we have to load firebase in the <head> of the page before we can initialize it in the script, see script here for example. popup.html will be the page that we will dynamically load based on the authentication state. It is linked to a script popup.js and only contains a div element #container. We will immediatly add an event listener to that popup window to send a message to the background script using the Messaging API.

window.addEventListener('DOMContentLoaded', (_event) => {
  console.log("DOM loaded and parsed");
  //Send a message to background script informing that the page is loaded
  chrome.runtime.sendMessage({type: "popupLoad"}, function (response) {
       if (response.type == "loggedIn") {
           let htmlString = /*Your extension UI's HTML*/;
           document.querySelector("#container").innerHTML(htmlString);
       } else if (response.type == "loggedOut") {
           let htmlString = /*Log in form HTML*/
           document.querySelector("#container").innerHTML(htmlString);
       };
  });

So what we have done here is send a message to the background script as soon as the DOM of our popup.html is fully loaded to query the authentication state. The background script will send us a response object of type loggedIn or loggedOut, allowing us to decide what interface we should load for our popup.

Now we look at the background script.

First you should initialize Firebase.

var config = {
    /*Your config parameters from the Firebase console*/
  };
firebase.initializeApp(config);

Now we need to add an message listener. Note that this will listen to any message from any Chrome environement (tab, window, popup script etc) so we have to make sure we're acting on the right type of message. This is why I used {type: "popupLoad"} in the popup script message creation. All my messages have at least a type attribute.

chrome.runtime.onMessage.addListener(
    function(message, _sender, _sendResponse) {
      if (message.type == "popupLoad") {
        //Get current authentication state and tell popup
        let user = firebase.auth().currentUser;
        if (user) {
          sendResponse({type: "loggedIn"});
        } else {
          sendResponse({type: "loggedOut"});
        };
      } else if (message.type == "other") {
          //handle other types of messages
      };
    });

What we have accomplished so far is that as soon as the user clicks on the extension icon, we are able to decide what the interface of the popup should be based on the authentication state. Of course if the user is logged out, they will see our log in / sign up form. The button of this form should have an event listener that sends a message (of type loginAttempt for example) to the background script, and then the background script should give a response to the popup script if the log in is successful which allows us to replace the #container content with our extension's UI. Here we see that an extension is actually a single page application.

I had to use the method firebase.auth().onAuthStateChanged() in the background script which is basically an event listener if that detects the changes in auth state if I'm not mistaken.

I won't go into further details but I hope this will be enough for beginners like me to wrap their heads around this and come up with a working prototype.

In short:

  • Don't use the Firebase UI library
  • Initialize Firebase in the background script
  • The extension is a single page application. Use the Messaging API to fetch the current authentication state or the authentication state changes from the background script to be able to decide what interface you want to serve the user

If anyone notices any issue with this answer, don't hesitate. Some credit goes to the numerous people that were kind enough to help me on dev Discord or the few developers that I emailed out of the blue and put me on the right track.

like image 161
Experience111 Avatar answered Oct 01 '22 08:10

Experience111