Table Of Contents
-
Introduction
- 1.1 Assumptions
- Firebase Console Setup
-
Form View
- 3.1 Forgot Password
- Update Firebase Connection File
- Services
- Route Guarding
-
State/Vuex
- 7.1 Auth State
- 7.2 Auth Actions
- Summary
- Repository
- About Quasar
1. Introduction
This article builds atop of the initial article, Initial Service & Structure published in the Quasar-Firebase series. We're going to look at the most common authentication type in most applications: Email.
Before we get started, a few assumptions are in order. In this article, we're going to be breaking down the interactions between Firebase services, the serverConnection
file, and our base
service file. We'll be harnessing our store for state management, so the assumption here is that you have a decent grasp of Vuex and its inner workings. The repository has the complete code base, so that is always a place for a bit of study if you're a little weak in Vuex, as well as keeping Vuex's API docs close at hand.
Implementing Vuex within the context of Quasar can be initially achieved in one of two ways. The first is durining initial project creation via the Quasar CLI prompt:
Or, if you already have a pre-existing application you'll have to set the store up manually. Take a look at the docs for the setup.
You'll have to create the directory structure and use the index.js
that is shown in the docs.
Also, keep in mind Quasar offers a nice set of commands via the CLI. One of them being new
. You can simply create new modules via the CLI:
$ quasar new store myModule
or
$ quasar n s myModule
Always be sure to update your store's index file to add the new module.
This is also a good spot to point out that we're going to be adding a new context to our application.
Note: This repo already contains a working Firebase API key. In order to set up your own project, you need to replace your Firebase config values in the .env
file and replace it with your own key from the first article.
Be sure to clone the repo and have the app to follow along with. Navigate to the respective app and run:
$ npm run dev
A final note, this code is for Vue v2 and Quasar v1.
2. Firebase Console Setup
To set up email authentication, you first have to enable it in your project in your Firebase console:
In the Firebase console, click on the Authentication menu section.
On the Sign-in method tab, enable the Email/Password sign-in method and click Save.
3. Form View
Now a form for the user to register or login is needed. Create a page called Auth.vue
. This page houses both registration and login, as well as a link to the, forgot password page.
$ quasar new page Auth
In Auth.vue.
we've created a basic user form for registering and logging in users.
Notice the use of the Vuex convenience method mapActions
.
Before we forget, the user needs to reset their password if needed. Using the quasar-cli create a new page:
$ quasar new page ForgotPassword
Update our routes file, by adding a new route inside of our /auth
path:
const routes = [
...
{
path: '/auth',
component: () => import('layouts/MyLayout.vue'),
children: [
{
path: 'forgotPassword',
name: 'ForgotPassword',
component: () => import('pages/ForgotPassword.vue')
},
...
]
4. Update Firebase Connection File
Now that the form is in place, we need to start to augment a few more files. First, let's take a look at our firebaseConnection
file:
/src/boot/firebaseConnection.js
import firebaseServices from '../services/firebase'
export default ({ store, Vue }) => {
const config = process.env.environments.FIREBASE_CONFIG
firebaseService.fBInit(config)
// Tell the application what to do when the
// authentication state has changed
firebaseServices.auth().onAuthStateChanged((user) => {
firebaseServices.handleOnAuthStateChanged(store, user)
}, (error) => {
console.error(error)
})
Vue.prototype.$fb = firebaseServices
store.$fb = firebaseServices
}
There are two new statements, one for handling the change of the authentication state, and one for controlling the application's rendering process concerning the route.
Here is where it gets a little tricky, and one of the challenging pieces when dealing with Firebase with Quasar. Just a little tinkering is needed to handle this scenario. The next section will talk specifically about what the mechanics are in our base
service to halt the rendering of our application while Firebase is initializing.
5. Services
A couple of additions are needed now that we're going to be authenticating users. One is a new email
service, and the other is modifying our base
service.
Take a look at the new service: email
.
/src/services/firebase/email.js
This service is very straight forward and provides an interface to the Firebase auth methods themselves. Add the reference of the email
service into our firebaseService
object in our index.js
file.
/src/services/firebase/index.js
Next, we have some updates on our base
service. As mentioned at the end of section 4, things are a little tricky when working with Firebase and not having access to when the Vue app is being initialized. Looking back at our serverConnection
boot file, we can now investigate the two statements in the function.
When Firebase's authentication state changes, we pass a store reference and the user state over to the method, handleOnAuthStateChanged
.
/**
* Handle the auth state of the user and set it in the auth store module.
* Also sets up redirection if the user loses authentication. The action
* method will determine where the application routes to.
* @param {Object} store - Vuex Store
* @param {Object} currentUser - Firebase currentUser
*/
export const handleOnAuthStateChanged = async (store, currentUser) => {
const initialAuthState = isAuthenticated(store)
// Save to the store
store.commit('auth/setAuthState', {
isAuthenticated: currentUser !== null,
isReady: true
})
// If the user loses authentication route
// them to the login page
if (!currentUser && initialAuthState) {
store.dispatch('auth/routeUserToAuth')
}
}
First, we want to make an initial authentication check regarding the user with our method, isAuthenticated
. Next, we will set an auth object into our auth store module describing the state of the user and the application.
/**
* @param {Object} store - Vuex store
* @return {Boolean} - isAuthenticated
*/
export const isAuthenticated = (store) => {
return store.state.auth.isAuthenticated
}
Doing this moves the internal state away from the service and in a global location in our store. As an application grows in complexity, relying on state in our store is a cleaner and more consistent way.
A convenience if, in the event a user loses their authentication, we route them immediately to the login screen.
if (!currentUser && initialAuthStateSet) {
store.dispatch('common/routeUserToAuth')
}
You can test this by going in dev tools and removing the auth token provided by Firebase
Next, we have our ensureAuthIsInitialized
method that will be utilized in our route guard coming up in our next section.
/**
* Async function providing the application time to
* wait for firebase to initialize and determine if a
* user is authenticated or not with only a single observable.
* https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onauthstatechanged
* @param {Object} store - Vuex store
* @returns {Promise} - A promise that return firebase.Unsubscribe
*/
export const ensureAuthIsInitialized = async (store) => {
if (store.state.auth.isReady) return true
// Create the observer only once on init
return new Promise((resolve, reject) => {
// Use a promise to make sure that the router will eventually show the route after the auth is initialized.
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
resolve()
unsubscribe()
}, () => {
reject(new Error('Looks like there is a problem with the firebase service. Please try again later'))
})
})
}
This is what allows us to stay on a requiresAuth
page and not route us back to login. Also, note the use of declaring the observable as an assigned function, and then later calling it. unsubscribe()
allows us to resolve the auth service once it's done initializing, and then unsubscribe listening to the observable which is useful when dealing with the navigational guards. If this isn't set up this way, you'll end up with multiple observables in memory every time your user is being routed around your app.
6. Route Guarding
Here we are guarding our routes.
/**
Setup the router to be intercepted on each route.
This allows the application to halt rendering until
Firebase is finished with its initialization process,
and handle the user accordingly
**/
Router.beforeEach(async (to, from, next) => {
const { ensureAuthIsInitialized, isAuthenticated } = firebaseServices
try {
// Force the app to wait until Firebase has
// finished its initialization, and handle the
// authentication state of the user properly
await ensureAuthIsInitialized(store)
if (to.matched.some(record => record.meta.requiresAuth)) {
if (isAuthenticated(store)) {
next()
} else {
next('/auth/login')
}
} else if ((to.path === '/auth/register' && isAuthenticated(store)) ||
(to.path === '/auth/login' && isAuthenticated(store))) {
next('/user')
} else {
next()
}
} catch (err) {
Notify.create({
message: `${err}`,
color: 'negative'
})
}
})
The route guard can grow and get more complicated as your needs change. Still, the most important thing here is to halt the application from routing until the Firebase service is done initializing by calling ensureAuthIsInitialized
.
Note the check if the user is authenticated and trying to navigate to either the login or registration route. The application will not let the user go to either one of those routes and move them directly to the user page for a better user experience.
7 State/Vuex
Since Vuex is available from the initial project creation, we place the authenticated state in our store back in the base service file in the handleOnAuthStateChanged
method.
- 7.1 Auth State /src/store/auth/state.js
export default {
isAuthenticated: false,
isReady: false
}
Earlier we mentioned the use of mapActions
in our Auth.vue page. Take a look at the methods that can be available via mapActions
.
Also, be sure to take a look at our mutation for our auth store that we are using in our base service during our handleOnAuthStateChanged
.
8. Summary
Building from our first article, we now have a starting point in which we can successfully create and log in users against the Firebase SDK, as well as reset their password. After a user is successfully authenticated, they are routed to a protected route that is only accessible once a user is authenticated correctly. We've also augmented our base
service to allow the Firebase SDK to finish it's initialization process so that we will not route a user back to the login screen if they are already correctly authenticated upon browser refresh.
9. Repository
Quasar-Firebase Repo: Email Authentication
Up next: User Profile
10. About Quasar
Interested in Quasar? Here are some more tips and information:
More info: https://quasar.dev
GitHub: https://github.com/quasarframework/quasar
Newsletter: https://quasar.dev/newsletter
Getting Started: https://quasar.dev/start
Chat Server: https://chat.quasar.dev/
Forum: https://forum.quasar.dev/
Twitter: https://twitter.com/quasarframework
Top comments (2)
Thanks @adamkpurdy for this excellent and well written series. It has been very helpful for me! I'm trying to add the phone sign-in option and I'm facing some challenges. Here's how I did it:
1. Create a
phone.js
file in theservices/firebase
directory, with the following content:2. Add the following to
services/firebase/base.js
3. Update
services/firebase/index.js
4. Add the following to
store/auth/actions.js
Now, In my
Signup.vue
file, I have a<div id="recaptcha-container"></div>
in the<template>
section, and the<script>
section looks like thisHowever, when I run this, I get the following error in the browser console:
Error in created hook: "Error: reCAPTCHA container is either not found or already contains inner elements!"
Any advice on how I can fix this?
Here's a screenshot:
Hi Victor. I used phone auth a while back on another project. I did a few things differently with success by using the mounted lifecycle method to assign the reCaptcha to the Vue instance.
Also, based on your code it looks like your passing only a single argument in your
authenticateUser
call. Where your function signature is set up for two arguments. Try removing your object data structure and just pass the two arguments.I did a couple of other things differently, but that might be what's holding you up. I have plans on releasing a phone auth article in the future once Quasar v2 gets out of beta. If you have any other problems feel free to come and find me in the Quasar discord. #firebase => @adam (EN/US)