Integrate Google Sign-in using Firebase Auth on your Google Apps Script web app

Bonus: We'll make use of Firebase's pre-built UI to authenticate users on our Google Apps Script web app.

Integrate Google Sign-in using Firebase Auth on your Google Apps Script web app
Integrate Google Sign-in using Firebase Auth on your Google Apps Script web app

Firebase authentication provides a free, multi-platform sign-in system that could help make sure that the workflows which require some form of user identification (especially, pertaining to google sign-in) get a lot more simpler to configure and consume within the google apps script realm.

Disclaimer: This post makes public use of firebase config object; if you're uncomfortable doing so, please proceed with caution. if it helps, firebase's official demo app too does the same as they define this config object as "...non-secret identifiers for your Firebase project..."

Problem statement

In case you already didn't know about this, there's an existing issue while accessing Apps Script based Web Apps from a "logged-in" user's point of view on the following fronts -

  1. the Web App behaves differently when logged in from more than a single account on your browser
  2. the Apps Script function responsible to detect the active or current user behaves differently when it comes to g suite and non-G Suite (i.e. Gmail) users

Demo

Here's a quick preview of the user details that can be captured using Google sign-in via Firebase's pre-built UI for web authentication -

google sign-in using firebase authentication on a google apps script web app
google sign-in using firebase authentication on a google apps script web app

Codebase

You can access the entire script on my GitHub repository here and here's the snapshots for reference -

code.gs file:

function doGet(e) {
  return HtmlService.createHtmlOutputFromFile('Index')
  .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
  .setTitle('FirebaseUI | Firebase Authentication');
}

function webAppUrl() {
  return ScriptApp.getService().getUrl();
}
code.gs

Index.html file:

<!DOCTYPE html>
<html>

<head>
    <base target="_top">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://www.gstatic.com/firebasejs/7.14.2/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/7.14.2/firebase-auth.js"></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" />
    <script>
        var firebaseConfig = { // enter the details below
            apiKey: "",
            authDomain: "",
            databaseURL: "",
            projectId: "",
            storageBucket: "",
            messagingSenderId: "",
            appId: ""
        };
        firebase.initializeApp(firebaseConfig);
    </script>
    <script>
        google.script.run.withSuccessHandler(function(url) {
            var uiConfig = {
                signInFlow: 'popup',
                signInSuccessUrl: url,
                signInOptions: [
                    firebase.auth.GoogleAuthProvider.PROVIDER_ID
                ],
            };
            var ui = new firebaseui.auth.AuthUI(firebase.auth());
            var user = firebase.auth().currentUser;
            if (user) {
                document.getElementById("signOut").style.display = "inline-block";
            } else {
                ui.start('#firebaseui-auth-container', uiConfig);
            }
        }).webAppUrl();
    </script>
    <script>
        initApp = function() {
            firebase.auth().onAuthStateChanged(function(user) {
                if (user) {
                    user.getIdToken().then(function(accessToken) {
                        document.getElementById('account-details').textContent = JSON.stringify({
                            displayName: user.displayName,
                            email: user.email,
                            emailVerified: user.emailVerified,
                            phoneNumber: user.phoneNumber,
                            photoURL: "user.photoURL", // remove quotes to get the photoURL
                            uid: user.uid,
                            accessToken: "hidden", // replace "hidden" with accessToken
                            providerData: "user.providerData" // remove quotes to get the photoURL
                        }, null, '  ');
                    });
                } else {
                    document.getElementById('account-details').textContent = '';
                }
            });
        };
        window.addEventListener('load', function() {
            initApp();
        });
    </script>
</head>

<body>
    <div class="container center-align">
        <br /><br /><h1>Firebase and Google Apps Script</h1><br /><h3>Login using your Google account via Firebase Auth</h3><br /><br />
        <div id="firebaseui-auth-container"></div>
        <div class="row">
            <div class="col s12 m3 offset-m4">
                <pre id="account-details" class="left-align"></pre>
            </div>
        </div>
        <button style="display: none" id="signOut" class="firebaseui-idp-button mdl-button mdl-js-button mdl-button--raised firebaseui-idp-google firebaseui-id-idp-button" data-provider-id="google.com" data-upgraded=",MaterialButton">
            <span class="firebaseui-idp-icon-wrapper"><img class="firebaseui-idp-icon" alt="" src="https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/google.svg"></span>
            <span class="firebaseui-idp-text firebaseui-idp-text-long">Sign out (Custom)</span>
        </button>
        
        <script>
            document.getElementById('signOut').addEventListener('click', function(event) {
                firebase.auth().signOut();
                reload();
            });
            function reload() {
                google.script.run.withSuccessHandler(function(url) {
                    window.open(url, '_top');
                }).webAppUrl();
            }
        </script>
        
    </div>
</body>

</html>
index.html

Prior reading

Prerequisites

  • a Google Cloud & Firebase accounts - these are 2 separate entities and you can sign-up for their respective free tiers

Setup

Firebase account

  1. you'd first need to setup your Firebase project in order to add the required details in your javascript project
    • make a note of the config object as you'd need that while configuring the Index.html file of your Apps Script Web App
  1. setup sign-in methods via Firebase's console: authnetication > Sign-in methods
    • for the purposes of this post, I've only enabled Google; however, you're free to experiment with the rest on your own accord
  2. on the same page as point # 2, when you scroll down a bit to the 'authorised domains' segment, you'd get to see a few domains already registered; do not delete any of those and proceed to add the following -
    • googleusercontent.com

That's all you'd need to do on the firebase side of things.

Google Apps Script

  1. to begin with, copy the exact code along with the file structure as shared in the codebase above.
  2. replacce the items from under the firebaseConfig variable with the details you captured by completing step 1 of the previous phase
  3. deploy the script as a web app which is accessible by anyone, even anonymous

this should complete the setup!

Gotchas

  1. the front-end of the web app may seem a bit glitchy i.e. even after signing-in, you may see the sign-in button but just refresh the page and that should disappear; the same goes for the sign-out button
  2. I did try configuring phone & email verification but gave up as soon as i hit the first error on those 😅 it should be easy enough to configure though; will continue updating this post as and when I have better solutions