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.
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With