Approach 1 (didn't work)
I tried the approach described in @Donflopez's answer, and it got me half the way there.
The problem is that I'm using Google Auth for my app, and it doesn't support custom-protocols in the redirect url. (Invalid parameter value for redirect_uri: Invalid scheme: my-custom-protocol://auth/handler)
Approach 2 (didn't work)
My idea was then to use a redirect URI like this (with a NodeJS express server to receive the data): http://localhost:XXXX/auth/handler
Sign-in-tab opening code:
// not sure if this is the correct url to try, but it's what was opened when firebase.auth().signInWithRedirect() was called
require("electron").shell.openExternal(
`https://${projectID}.firebaseapp.com/__/auth/handler`
+ `apiKey=${apiKey}&appName=%5BDEFAULT%5D&authType=signInViaRedirect`
+ `&providerId=google.com&scopes=profile&v=7.7.0`
+ `&redirectUrl=http%3A%2F%2Flocalhost%3A${oauthPort}%2Fauth%2Fhandler`);
OAuth data receiver (NodeJS express server):
const express = require("express");
const oauthPort = XXXX;
const redirectServer = express();
redirectServer.get("/auth/handler", (req, res)=>{
debugger;
// I inspected the request data with debugger, but found nothing useful!
});
However, I wasn't able to get that working. While I was able to set up a NodeJS express server to try to receive the data, when the actual request came in (for localhost:XXXX/auth/handler, after the browser tab redirected to it), I didn't see anything in the request url or headers that looked like credential data.
If someone figures out how to extract/convert the request data into credential data, let me know! (In the meantime, I use the slightly more complex approach below.)
Approach 3 (worked)
My next idea (which works) was to just have my Electron app open a tab in an external browser, pointed to the same domain as in the Electron app, but to a special page that then launches the Google sign-in popup for the website:
require("electron").shell.openExternal(`${electronApp_domain}/sign-in-helper`);
(If your Electron page is served locally [eg. file:///], you may need to add a local express server to serve the sign-in-helper page for the external-browser tab -- not sure if Google sign-in works on file:/// pages.)
That page then retrieves the Google id-token and access-token like so:
const firebase = require("firebase");
firebase.auth().signInWithPopup(provider).then(result=>{
const idToken = result.credential.idToken;
const accessToken = result.credential.accessToken;
// You now have the id-token and access-token.
// Either one is sufficient to sign-in your Electron app, just need to transfer it.
TransferTokensToElectronApp(idToken, accessToken);
});
For transferring it to Electron, I did this:
const desktopApp_receiveTokensPort = XXXX;
function TransferTokensToElectronApp(idToken, accessToken) {
const script = document.createElement("script");
script.src = `http://localhost:${desktopApp_receiveTokensPort}/receive-tokens/?idToken=${idToken}&accessToken=${accessToken}`;
document.head.appendChild(script);
// after 1s, remove the script element, and close the page
setTimeout(()=>{
script.remove();
window.close();
}, 1000);
}
And the receiving code in the Electron app:
const express = require("express");
const receiveTokensServer = express();
const receiveTokensPort = XXXX;
const receiveTokensServer_domainStr = `http://localhost:${receiveTokensPort}`;
receiveTokensServer.get("/receive-tokens", (req, res)=>{
const url = new URL(`${receiveTokensServer_domainStr}/${req.url}`);
const idToken = url.searchParams.get("idToken");
const accessToken = url.searchParams.get("accessToken");
console.log("Received sign-in data from web helper:", {idToken, accessToken});
SendTokensToRenderer(idToken, accessToken);
});
receiveTokensServer.listen(receiveTokensPort);
Implementation of the SendTokensToFrontEnd function depends on how you perform communication in your app, but an example using Electron's ipc system:
function SendTokensToRenderer(idToken, accessToken) {
browserWindow.webContents.send("token-transfer-channel", {idToken, accessToken});
}
And then finally, to receive those tokens in your renderer/frontend, and use it to sign-in to Firebase:
const {ipcRenderer} = require("electron");
const firebase = require("firebase");
ipcRenderer.on("token-transfer-channel", (event, data)=>{
const cred = firebase.auth.GoogleAuthProvider.credential(data.idToken, data.accessToken);
firebase.auth().signInWithCredential(cred);
});
EDIT: Found a tutorial soon after, that uses a similar approach (except it transfers the credentials to the frontend through a shared database, rather than a localhost "script" request): https://pragli.com/blog/how-to-authenticate-with-google-in-electron