Setup Firebase Email Authentication (Web) with Whitelisted emails using Vue.js and Vue Router

Setup Firebase (Web)

Create a Firebase project: https://console.firebase.google.com

Register your app with Firebase: Firebase Console -> Project Overview -> + Add app -> Web

Enable Firebase Console -> Authentication -> Sign-in Method: Email / Password

NOTE: You can pre-create users at Users tab and click on Add User.

Add Firebase SDK: https://firebase.google.com/docs/web/setup#add-sdks-initialize. You can do so via Firebase Hosting, CDN, or module bundlers/Node.js.

Vue.js

Since I am using vue-cli, I will use module bundlers.

Go to Vue.js project directory.

npm install --save firebasenpm install --save firebaseui

Edit main.js

import * as firebase from 'firebase/app'// import 'firebase/analytics';import 'firebase/auth'import * as firebaseui from 'firebaseui'import 'firebaseui/dist/firebaseui.css'const firebaseConfig = {  apiKey: "AIza...",  authDomain: "....firebaseapp.com",  databaseURL: "https://....firebaseio.com",  projectId: "...",  storageBucket: "....appspot.com",  messagingSenderId: "1020...",  appId: "1:1020..."};firebase.initializeApp(firebaseConfig)

NOTE: Get firebaseConfig from Firebase Console -> Project Overview (Click on Gear Icon) -> Project Settings -> General -> Your apps -> Select your Web apps -> Firebase SDK snippet -> Config.

Edit main.js after firebase.initializeApp(firebaseConfig).

const app = {  name: 'My App',  version: '1.0.1',  // isAuthenticated: firebase.auth().currentUser !== null  isAuthenticated: false}Vue.prototype.$app = applet initUi = falsefunction renderUi() {  if (!initUi) {    new Vue({      router,      render: h => h(App)    }).$mount('#app')    initUi = true  }}firebase.auth().onAuthStateChanged(user => {  // user is not enough, as we need custom claims as well  // app.isAuthenticated = user !== null  if (user) {    user.getIdTokenResult()      .then((idTokenResult) => {        app.isAuthenticated = idTokenResult.claims.authorized        renderUi()      })  }  else {    app.isAuthenticated = false    renderUi()  }  // onAuthStateChanged is slightly delayed,  // thus we wait for authentication before render to make sure router navigation guard works  // renderUi})

Vue Router

I am using vue-router, so I am using Navigation Guards for authentication.

Edit router/index.js

  • If not authorized, redirect to /signin.
import Vue from 'vue'import VueRouter from 'vue-router'import Home from '../views/Home.vue'import Page from '../views/Page.vue'import SignIn from '../views/SignIn.vue'Vue.use(VueRouter)const routes = [  {    path: '/',    name: 'home',    component: Home  },  {    path: '/page/:id',    component: Page  },  {    path: '/about',    name: 'about',    // route level code-splitting    // this generates a separate chunk (about.[hash].js) for this route    // which is lazy-loaded when the route is visited.    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')  },  {    path: '/signin',    name: 'signin',    component: SignIn  },]const router = new VueRouter({  mode: 'history',  base: process.env.BASE_URL,  routes,  scrollBehavior (to, from, savedPosition) {    if (savedPosition) {      return savedPosition    } else {      return { x: 0, y: 0 }    }  }})import * as firebase from 'firebase/app'router.beforeEach((to, from, next) => {  if (to.fullPath === '/about') {    next()    return  }  const isAuthenticated = Vue.prototype.$app.isAuthenticated  if (!isAuthenticated && to.fullPath !== '/signin') next('/signin')  else next()})export default router

SignIn Component

Edit views/SignIn.vue

<template>  <div>    <div class="alert alert-danger" role="alert" v-if="error">      {{ error }} <button type="button" class="btn btn-primary" @click="signIn">Try again</button>    </div>    <div id="firebaseui-auth-container"></div>  </div></template><script>import * as firebase from 'firebase/app'import * as firebaseui from 'firebaseui'export default {  name: 'signin',  data() {    return {      error: null,      from: null,      uiConfig: {        callbacks: {          // @ts-ignore          signInSuccessWithAuthResult: (authResult, redirectUrl) => {            // this.$app.isAuthenticated = true            // this.$router.replace(this.from || '/')            const user = authResult.user            this.handleSignIn(user)            return false // redirect if true          },          uiShown() {             // login from is shown          }        },        // signInFlow: 'popup',        // signInSuccessUrl: '/',        signInOptions: [            {              provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,              requireDisplayName: false            }        ]      }    }  },  methods: {    signIn() {      this.$ui.start('#firebaseui-auth-container', this.uiConfig)    },    handleSignIn(user) {      user.getIdTokenResult(true)        .then((idTokenResult) => {          this.$app.isAuthenticated = idTokenResult.claims.authorized          if (!this.$app.isAuthenticated) { // custom claims not updated/received yet            // refresh again            console.log('refresh again')            this.handleSignIn(user)          }          else {            this.$router.replace(this.from || '/')          }        })        .catch((error) => {          if (error.code == 'auth/user-token-expired') {            this.error = `${user.email} is not authorized.`            this.$app.isAuthenticated = false            // this.signIn()          }        })    }  },  mounted() {    this.$ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebase.auth())    this.signIn()  },  beforeRouteEnter(to, from, next) {    next(vm => {      vm.from = from    })  }}</script>

Cloud Functions

We need a cloud functions to which listen to user account creation and whitelist the emails

  • set custom claims if email is valid
  • delete account if email is invalid
const functions = require('firebase-functions')const admin = require('firebase-admin')admin.initializeApp()// TODO: retrieve authorizeEmails from firestore insteadconst authorizeEmails = [  '[email protected]',  '[email protected]'];exports.authorizeUserByEmail = functions.auth.user().onCreate(async (user) => {  const uid = user.uid  const email = user.email  if (authorizeEmails.includes(email)) {    await admin.auth().setCustomUserClaims(uid, {authorized: true})    console.log(`setCustomUserClaims: ${email}`)    return true  }  else {    await admin.auth().deleteUser(uid)    console.log(`Delete user: ${email}`);    return false  }});

NOTE: Learn about write and deploy cloud functions.

More

References:

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.