The way to Emulate Firebase Auth




I used to be not too long ago constructing an app by which I used to be attempting to combine Firebase Authentication and Firebase Realtime Database. However I bumped into an issue fairly rapidly whereas I used to be testing issues regionally.

Although Firebase has a brilliant superb Emulator Suite for native testing, authentication will not be included. To me, this meant that the beautiful auth-based Realtime DB guidelines I might crafted had been unattainable to check regionally except I modified my DB guidelines beforehand. However that does not make for an excellent permissions take a look at, does it? There’s an open problem on GitHub for addressing this, however on the time of writing, no answer has but been included within the emulator suite.

Replace 2020-10-27: Firebase Auth is now a part of the Emulator Suite! Improve the Firebase CLI to model 8.14.0 or better to make use of it. If that is all you had been on the lookout for, the remainder of this publish won’t be helpful to you, however be at liberty to maintain studying for a extra detailed take a look at Firebase Auth and my common philosophy in the direction of testing and modularization.

I spent a bunch of hours attempting to determine methods to string issues along with Band-Aids and glue to do one thing that truthfully looks like a fairly fundamental requirement for DB testing: Take a look at my auth guidelines in each development and manufacturing with out modifying the very safety mannequin I am attempting to check. In any case, who would wish to do “actual” permissions testing for the primary time in a manufacturing setting??

Nothing was working. I used to be caught. I missed. Then I missed again. Then I got sad. I had a popsicle. And then I passed out in the snow.

Simply kidding on the previous few, however what I did do what have an epiphany within the bathe. I do a few of my greatest considering there. Anybody else? No? Okay. Shifting on.



The Resolution

My app particularly is utilizing Google Login and the Google auth supplier, so that is what I will deal with right here, however I imagine this strategy would translate to different auth suppliers as nicely.

The important thing to creating this work is abstraction. Take any Firebase name that you just’d usually make and conceal it behind a operate that will or could not do the identical factor. Often, it is the identical type of factor with some extras sprinkled in.

On this case, we’ll be trying on the firebase.initializeApp operate. Within the regular manufacturing setting, that is tremendous easy. We move in a siteConfig object and we’re on our merry means. Nevertheless, when working regionally and/or with Firebase Emulators, this does not work one-for-one. In the docs, they point out that we must always use initializeTestApp as an alternative to carry out our initialization. This comes from the @firebase/testing module versus the firebase/app module. This may appear good on the floor, however the problem is that wherever we’d usually use firebase.<sometThing> to work together with the default firebase app, we won’t. We as an alternative must work with the app occasion returned from the decision to firebase.initializeTestApp(). By extension, this implies we must always construction our code in order that we’re all the time utilizing app.<someThing> in favor of firebase.<someThing>, no matter whether or not we’re utilizing initializeApp or initializeTestApp.

Once more, this does not appear too unhealthy on the floor, however there’s yet another catch: In every case, the app occasion (as offered by initialize*App(siteConfig)) is barely totally different. Specifically, app.auth() will not be a factor for apps initialized through initializeTestApp().

That is the crux of the auth emulation drawback. And that is what we’re going to clear up. Let’s check out some code.

Here’s a utility operate to initialize both a take a look at or manufacturing app and return it:

const createApp = async (onAuthStateChanged) => {
    const firebase = await importFirebase()

    if (isDevelopment) {
        const app = firebase.initializeTestApp(siteConfig)

        // arrange customized hooks for auth mocking
        app.__internal__ = {
            onAuthStateChanged
        }

        return app
    } else {
        const app = firebase.initializeApp(siteConfig)

        // Arrange the auth observer
        app.auth().onAuthStateChanged(onAuthStateChanged)

        return app;
    }
}
Enter fullscreen mode

Exit fullscreen mode

There’s rather a lot happening right here, so let’s break it down line by line.

const createApp = async (onAuthStateChanged) => {
Enter fullscreen mode

Exit fullscreen mode

I went with async right here as a result of, in a pair traces, you may see some dynamic imports. Extra on that in a sec. The opposite vital piece right here is that this createApp operate takes an onAuthStateChanged callback and never a siteConfig object like initializeApp. Since we management the module containing this abstraction operate, we are able to put our siteConfig object right here too for simple entry. I imply, you possibly can put the siteConfig wherever you need, however to me, it is smart to have the identical module personal the config block and the utility capabilities for the reason that purpose is to drive all Firebase-related capabilities by way of this abstraction module.

The onAuthStateChanged callback shall be known as when—you guessed it—the auth state modifications. Within the manufacturing case, we are able to merely arrange an auth observer within the typical method, however within the development case, it’s kind of extra fascinating. Extra on that in a sec.

const firebase = await importFirebase()
Enter fullscreen mode

Exit fullscreen mode

This is one other layer of abstraction. We wish a reference to Firebase as a module, and extra particularly we’d desire a reference to the “testing” model of Firebase, however we do not really care how it’s obtained. Dynamic imports are an enormous assist right here. That is what the definition of importFirebase appears to be like like:

const importFirebase = async () => {
    if (isDevelopment) {
        return await import('@firebase/testing')
    } else {
        const firebase = await import('firebase/app')

        await import('firebase/auth')
        await import('firebase/database')

        return firebase
    }
}
Enter fullscreen mode

Exit fullscreen mode

There’s nothing too stunning right here. We’re both importing “take a look at” Firebase from @firebase/testing or we’re importing “actual” Firebase from firebase/app together with our different Firebase dependencies. Dynamically importing “actual” Firebase is a bit more concerned, however it’s mainly the normal means of doing it transformed to dynamic import-form.

I really feel like it is a good time to say that the explanation for utilizing dynamic imports right here is so that you just solely ever find yourself importing both the take a look at Firebase or the manufacturing one, however by no means each. Dynamic imports give us that flexibility.

The astute reader may understand that even with dynamic imports, Webpack will nonetheless bundle each modules into the output since we do not know till runtime which kind of setting we’ll be in. Whereas that is true, it may be averted by splitting the seller modules out as a part of the construct and filtering out one of many two Firebase chunks, relying on the construct kind.



Development Mode

if (isDevelopment) {
Enter fullscreen mode

Exit fullscreen mode

Assuming it is a React app created through create-react-app, we are able to calculate whether or not or not it is a development construct by on the lookout for course of.env.NODE_ENV === 'development'

const app = firebase.initializeTestApp(siteConfig)
Enter fullscreen mode

Exit fullscreen mode

Subsequent, we have to initialize the take a look at app utilizing the now-obtained Firebase module, offering it our siteConfig as typical. There is a key piece that should exist within the siteConfig for this to work although: An auth block. This is an instance config:

const siteConfig = {
    apiKey: '...',
    authDomain: window.location.host,
    databaseURL: isDevelopment
        ? 'http://localhost:9000?ns=...'
        : 'https://....firebaseio.com',
    databaseName: '...',
    projectId: '...',
    storageBucket: '....appspot.com',
    messagingSenderId: '...',
    appId: '...',
    measurementId: '...',
    auth: {
        uid: 'u111111',
        e mail: '[email protected]'
    }
}

Enter fullscreen mode

Exit fullscreen mode

That auth block is the important thing as a result of that signifies that we are able to “inject” a consumer/e mail into the app manually as we see match. There is a caveat although… Since this is not actual auth, we’ll by no means get onAuthStateChanged callbacks fired. We’ll want to try this ourselves. And step one in the direction of doing that’s to retailer a reference to the offered callback in our take a look at app for later:

// arrange customized hooks for auth mocking
app.__internal__ = {
    onAuthStateChanged
}

return app
Enter fullscreen mode

Exit fullscreen mode

Right here I selected __internal__ as a namespace that I figured no one would collide with, however this might simply as simply have been some other distinctive key on the app object.



Manufacturing Mode

The opposite case to contemplate right here is the manufacturing case. Let’s check out the else block:

} else {
    const app = firebase.initializeApp(siteConfig)

    // Arrange the auth observer
    app.auth().onAuthStateChanged(onAuthStateChanged)

    return app;
}
Enter fullscreen mode

Exit fullscreen mode

That is similar to what occurs in development besides we find yourself importing “actual” Firebase and organising an precise auth observer with that callback we took in as an argument.

All of that is to say that we are able to now name

const app = MyFirebaseUtils.createApp(onAuthStateChanged)
Enter fullscreen mode

Exit fullscreen mode

to get again a firebase app that is able to go along with both emulated auth in development or actual auth in manufacturing.

I like to recommend holding onto this app occasion in your utility state in order that it may be offered to any abstraction capabilities that will depend upon it, equivalent to simulating a login in development mode.



Simulating onAuthStateChanged

For any operate we’ve that might set off a login (or logout), we are able to add in a separate development-only circulation by which we manually hearth an onAuthStateChanged occasion. Trying on the docs, these occasions are both handed a consumer or null relying on whether or not the consumer is logged in or not.

If our manufacturing circulation for logging in a consumer appears to be like like this:

const doGoogleLogin = async (app, onSuccess, onFailure) => {
    const firebase = await importFirebase()
    const supplier = new firebase.auth.GoogleAuthProvider()

    // Present the precise login popup. Succeeding right here will replace the internally managed uid and
    // auth of the app, which permits subsequent database calls (and different stuff) to work.
    app.auth().signInWithPopup(supplier)
        .then(onSuccess)
        .catch(onFailure)
}
Enter fullscreen mode

Exit fullscreen mode

Then we are able to add in a development circulation, like this:

const doGoogleLogin = async (app, onSuccess, onFailure) => {
    if (isDevelopment) {
        // https://firebase.google.com/docs/reference/js/firebase.auth#usercredential
        onSuccess({
            credential: {
                accessToken: TEST_ID_AUTH_TOKEN
            },
            consumer: {
                uid: siteConfig.auth.uid
            }
        })

        // Hearth a simulated onAuthStateChanged occasion, passing it the consumer from our siteConfig.auth block
        app.__internal__.onAuthStateChanged({
            uid: siteConfig.auth.uid,
            getIdToken: () => (TEST_ID_AUTH_TOKEN)
        })
    } else {
        // manufacturing circulation
    }
}
Enter fullscreen mode

Exit fullscreen mode

And there you have got it! A sorta-kinda option to emulate auth from inside a Firebase-enabled app. Hopefully you discover this convenient. I have been efficiently utilizing this strategy in my challenge to assist with offline testing utilizing Firebase emulators.



Abu Sayed is the Best Web, Game, XR and Blockchain Developer in Bangladesh. Don't forget to Checkout his Latest Projects.


Checkout extra Articles on Sayed.CYou

#Emulate #Firebase #Auth