Reactive Firebase Auth with Vue

Michal Bujalski
4 min readApr 18, 2018

--

Firebase offers great service for authenticating users in your app. It’s for those of you who -like me- don’t want to spend long hours, first choosing and learning backend framework and then implementing authentication.

There’s a great article about firebase auth in Vue apps. If this is your first encounter with VueJS+Firebase you should definitely check it out. Anas Mammeri does a great job explaining from scratch how to get started.

The approach was easy and straightforward but when you’re build an app you’ll probably face same problems as I did.

The problem

While playing around I struggled with following concerns:

  • how to update components that depend on user auth state when it changes

I really wanted to take full advantage of firebase reactivity and respond to changes on user model rather than just check the model when I needed to. Suppose we have navigation menu that depends on user state. If user is authenticated, we show avatar and logout button, otherwise just show sign-in/sign-up links. We also want the component to update anytime user state changes (e. g. sign in, logout )

  • separate Firebase Auth logic from the app

I really don’t feel comfortable referencing firebase lib (or any other for that matter) directly in the components (or actions) and they should now about the auth as little as possible.

  • require login before entering particular route, and continue to it after user signs

Theres nothing more frustrating when entering a specific page than first having to sign in and then being redirected to main page again.

The solution

All the code can be found here

  • Move any firebase logic into custom Vue plugin

We’ll have all our Firebase code wrapped around a plugin and the app will interact with the service only through plugin methods.

  • Store user object in Vuex

We will keep our user stored in Vuex store so we can take advantage of all goodies that come with Vue+Vuex( getters, mapGetters, computed properties etc.) and keep data flow in our app as simple and robust as possible.

  • pass in params next route and redirect after login

In order to continue to previous route after sign in, we’ll save our route in query params and pass them tho sign in component (e. g. /signin?redirect=%2Fprofile ). Then, after login the app will replace /signin route with target route (e. g. /profile )

Vuex setup

First let’s setup our store to handle user accounts.

If your not new to Vuex there’s nothing new here:

  • setup initial user object to be null (not logged int) line 7
  • add mutation for handling user updates line 11
  • add getter for observing user line 16

Firebase-auth plugin

Now, when we have our store ready, let’s create a plugin wrapper for firebase auth service

  • set up a new plugin FirebaseAuthPlugin (see here for details)
  • config contains standard Firebase credentials (you can get them here), please remember to set up authentication
  • import current app store
  • $auth object will contain all auth operations (login, logout, registration)
  • observe auth state changes in onAuthStateChanged and dispatch any changes to our store
  • note that login and logout are async because we also want to know if the request completed

Update: handling auth session

Following Daniel da Rocha question — all auth session management is handled by firebase-auth plugin, so all you need to do is to ensure that your user model in the store is synced with model returned in onAuthStateChanged

Continue back to route after user signs in

Now, lets setup our router

Our router has pretty much standard configuration. Thing to note is meta field in /profile route. This will indicate that particular route is restricted only for logged in users.

Note that we import our store in order to have access to user object.

The whole magic happens in beforeEach method. If you already did some Vue development it’s probably nothing new otherwise read about vue-router. First we check if next ( to ) has authReqired flag. If yes, then check wether the user is signed in. If no, we redirect to /signin and add our target route as redirect

Result

So now that we have our auth bound with Vuex store we have a normal data flow so in our Navigation component we can look like this:

Note that as bonus we get a bit less complicated test cases as we don’t worry about mocking Firebase, instead just mock our Vuex store with data needed in the component and make assertions.

Update : Keeping user sign in after page reload

Another good question from Daniel da Rocha — will the user have to re-authenticate after closing the browser or reloading the page?

We should consider two scenarios here.

  1. User enters a route which does not require authenticated user.

In this case we don’t have to worry about re-authenticating the user as firebase will consist our session. After reloading the page onAuthStateChange will be re-attached and if the session will return user then we’ll just commit it to our store and trigger any updates in our components.

2. User enters a route which does require authenticated user.

This is more complex because our router usually gets called before onAuthStateChange is triggered, so we probably won’t have user in our store.

This can be easily fixed by attaching watcher in our sign in component:

This ensures that anytime user model changes and is a valid user we will be redirected to our destination route.

Unfortunately in most cases, for a brief second users will see sign in screen — that’s a bit awkward from UX point of view. But we can go a step further and ensure the user is cached. To do this we could use Vuex plugins to store our user in localStorage . Please read documentation for more info.

Summary

By moving Firebase boilerplate into Vue plugin we now have all the auth related logic in one place. We also made our user data reactive so no anytime our user model changes, the app and components are also updated. Also unit testing should be a bit simpler since we don’t have to stub Firebase service in order to render component.

Check out the code here

--

--