Combining Firebase Authentication with Vue Router

I am currently working on authenticating a Vue.js application using Firebase.

One issue I have encountered is that when attempting to access a URL that requires login directly while already logged in, the router checks for authentication state before Firebase has had a chance to return the auth response. As a result, the user gets redirected to the login page even though they are already authenticated.

Is there a way to delay Vue Router navigation until the authentication state has been retrieved from Firebase? I noticed that Firebase stores authentication data in localStorage - would it be secure to use this as a preliminary authentication check? Ideally, I would like to display a loading spinner or some other indicator while the user's authentication status is being verified, and only allow them to access the desired page once authenticated.

In my router/index.js file:


let router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/login',
      name: 'Login',
      component: Login
    },
    {
      path: '/example',
      name: 'Example',
      component: Example,
      beforeEnter: loginRequired
    }
})

function loginRequired (to, from, next) {
  if (authService.authenticated()) {
    next()
  } else {
    next('/login')
  }
}

In my auth.js file:


import * as firebase from 'firebase'

var config = {
    // firebase config
}

firebase.initializeApp(config)

// Rest of the code here...

In my app.vue file:


<template>
  <div id="app">
    <p v-if="auth.user !== null">Logged in with {{ auth.user.email }}</p>
    <p v-else>not logged in</p>
    <router-view v-if="auth.user !== null"></router-view>
  </div>
</template>

<script>
import authService from './auth'

export default {
  name: 'app',
  data () {
    return {
      auth: authService
    }
  }
}
</script>

Answer №1

When Firebase starts up, it triggers an authentication state change event, but this doesn't happen immediately.

To ensure that Firebase has completed its user/authentication initialization process, you should modify authService.authenticated to return a promise.

const initializeAuth = new Promise(resolve => {
  // This sets up a hook for the initial auth-change event
  firebase.auth().onAuthStateChanged(user => {
    authService.setUser(user)
    resolve(user)
  })
})

const authService = {

  user: null,

  authenticated () {
    return initializeAuth.then(user => {
      return user && !user.isAnonymous
    })
  },

  setUser (user) {
    this.user = user
  },

  login (email, password) {
    return firebase.auth().signInWithEmailAndPassword(email, password)
  },

  logout () {
    firebase.auth().signOut().then(() => {
      console.log('logout done')
    })
  }
}

It's unnecessary to call setUser from the signInWith... promise because this will already be taken care of by the initializeAuth promise.

Answer №2

Expanding upon Richard's previous response, specifically for individuals utilizing traditional Vue (rather than Vuex)

Main Application Setup in main.js:

//initialize firebase
firebase.initializeApp(config);
let app: any;
firebase.auth().onAuthStateChanged(async user => {
if (!app) {
    //wait to get user
    var user = await firebase.auth().currentUser;

    //start the application
    app = new Vue({
      router,
      created() {
        //redirect if user is not logged in
        if (!user) {
          this.$router.push("/login");
        }
      },
      render: h => h(App)
    }).$mount("#app");
  }
});

Routing Configuration in router.js:

//route definitions
//...
router.beforeEach((to, from, next) => {
  const currentUser = firebase.auth().currentUser;
  const requiresAuth = to.matched.some(record => record.meta.requiresAuth);

  if (requiresAuth && !currentUser) {
    const loginpath = window.location.pathname;   
    next({ name: 'login', query: { from: loginpath } });
  } else if (!requiresAuth && currentUser) {
    next("defaultView");
  } else {
    next();
  }
});

Answer №3

After encountering the same issue, I decided to postpone creating the Vue object until the first onAuthStateChanged event.

# main.js
// wait for initial firebase auth change before initializing vue
import { AUTH_SUCCESS, AUTH_LOGOUT } from "@/store/actions/auth";
import { utils } from "@/store/modules/auth";
let app;
firebase.auth().onAuthStateChanged(async user => {
  if (!app) {
    if (user) {
      await store.dispatch(AUTH_SUCCESS, utils.mapUser(user));
    } else {
      await store.dispatch(AUTH_LOGOUT);
    }
    app = new Vue({
      router,
      store,
      i18n,
      render: h => h(App)
    }).$mount("#app");
  }
});

In my routes, I handle the authentication process as usual. If a user lands on the login page, they are automatically redirected to my overview dashboard.

#router.js
router.beforeEach((to, from, next) => {
  let authenticated = store.getters.isAuthenticated;
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // check if user is logged in and redirect accordingly
    if (!authenticated) {
      next({
        name: "Login",
        query: { redirect: to.fullPath }
      });
    } else {
      next();
    }
  } else {
    // if already authenticated and trying to access login page, go to overview instead
    if (authenticated && to.name === "Login") {
      next({
        name: "Overview"
      });
    }
    next(); // always call next()!
  }
});

Answer №4

You are presented with two alternatives:

1) Utilize the beforeRouteEnter method within the component:

export default {
        name: "sample",
        ....
        beforeRouteEnter(to, from, next){
            if (authService.isAuthenticated()) {
                next()
            } else {
                next('/login')
            }
        },
}

2) employ the beforeResolve function from the router.

router.beforeResolve((to, from, next) => {

    if(to.fullPath === '/example' && !authService.isAuthenticated()){
        next('/login')
    }else{
        next()
    }
})

Vue route guards lifecycle overview

Answer №5

If you want to postpone the authorization state, simply follow these steps:

  • a) Prior to mounting the app, utilize the firebase auth onAuthStateChanged method.
  • b) Within your router file, include a meta tag to the main route where you wish to implement login protection.
  • c) Implement a router.beforeEach function for authentication and define how redirection of users should be handled. Examples for each scenario are provided below: Stage a)
    firebase.auth().onAuthStateChanged(function(user) {
        console.log(user)
        new Vue({
            router,
            render: h => h(App),
          }).$mount('#app')
        }
    });

Stage b)

    ....
    ....
    ....
    {
        path: "/dashboard",
        name: "dashboard",
        component: Dashboard,
        meta: { requiresAuth: true },//Include this
        children: [
            {
                path: "products",
                name: "products",
                component: Products,
            },
        ],
    ....
    ....
    ....

Stage c)

    router.beforeEach((to, from, next) => {
        const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
        const currentUser = firebase.auth().currentUser
        if(requiresAuth && !currentUser) {
            next("/")
        } else if(requiresAuth && currentUser) {
            next()
        }else{
            next()
        }
    })

By following these steps, you should be all set.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

ng-repeat dysregulating the binding of directive models

<input-directive model="config.shared.product.whatevers[0]"></input-directive> <!-- This code is functioning correctly, however the following does not bind properly --> <td ng-repeat="whatever in config.shared.product.whatevers trac ...

PNotify is throwing a SyntaxError stating that the import for defaultModules cannot be found

Trying to display a simple notification in a web browser using PNotify on a thymeleaf HTML page. Added the following WebJar dependencies to the pom.xml file: <dependency> <groupId>org.webjars.npm</groupId> <artifactId& ...

What is the best method for locating the ID of a specific element tied to a dynamically generated button that presents profile information using javascript?

My goal is to show unique user information when the user hovers over a button. This information is contained in a specific div element with an ID that corresponds to the button being hovered over. The challenge I am facing is extracting the ID of the info ...

Encountering a React JS error while compiling projects

Every time I attempt to compile my project, everything goes smoothly until it reaches the database call, where I encounter this error: ./node_modules/msnodesqlv8/lib/sqlserver.native.js module not found: Error: Can't resolve 'fs' ./node_mo ...

Is there a way to retrieve two distinct data types from a single ng-model within a select option?

My mean stack code is functioning well, but I am looking to enhance it by adding a new feature. Specifically, I want to retrieve more elements from my NoSql database while selecting options. This is the structure of my database: Tir2 :id, price, xin, yin ...

What is the process for importing credentials securely?

My application is built on .NET 4.7.2 and utilizes JavaScript to dynamically import additional scripts. The issue I'm facing is that the application is throwing a 401 unauthorized error when trying to fetch these files. I have verified that the user ...

I'm facing a challenge with discord.js where I can't seem to get multiple prefixes to work. Do you have any suggestions on how I can modify it to

As the title suggests, I've been experimenting with different versions of the code below. The current version can recognize firstPrefix but not secondPrefix. I simply want my discord bot to be able to identify both prefixes and split the Args accordin ...

Using base64 encoding and setting the binary type for Websockets

I'm feeling a bit lost. I've been working on understanding the mechanics of Websockets by setting up a server in node.js and a client-side application. One thing that's really puzzling me is how Websockets handle data transmission. Should da ...

Upon running the command "React + $ npm start," an error occurred with the code 'ERR_OSSL_EVP_UNSUPPORTED' related to opensslErrorStack

When running $npm start, an error is being thrown: opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ], library: 'digital envelope routines', reason: 'unsupported', code: 'ERR_OSSL_EVP_ ...

AngularJS: Enhancing Date Display and Organization

Looking to change the date format from "Mon Oct 12 2015 00:00:00 GMT+0530 (IST)" to "YYYY/MM/DD" within my controller. ...

Utilize buttons to send data to a PHP script

Currently, I am in the process of developing a control panel for an application, consisting of an array of buttons. My aim is to relay information about the specific button pressed to the designated script responsible for executing commands. However, I am ...

What are the steps for utilizing a piece of information from one table to retrieve additional data from a separate table in Firebase?

I retrieved a list of data from a table named 'appointment'. Now, I need to extract the data named 'theraid' from this table in order to retrieve the name from another table named 'thera'. However, I am unsure how to accomplis ...

Having trouble manipulating state in JavaScript for React Native?

Encountering an issue when attempting to use the setState function in React Native. Code Snippet import React from "react"; import { TextInput, Text, View, Button, Alert } from "react-native"; const UselessTextInput = () => { st ...

Executing a search and replace function using a delay within a foreach loop - the ultimate guide

Below is a snippet of code where I attempt to perform find and replace within an array by searching for keys and replacing them with corresponding values. However, the expected functionality does not work as intended, leading to multiple searches for &apos ...

Unable to access localStorage

I created a Plunker to store a value in localStorage: <!DOCTYPE html> <html> <script> localStorage.setItem('test', "hadddddha"); </script> </html> Then, I built a test page to retrieve the value from local ...

Does angular-sortablejs work with angular 5?

I attempted to use angular-sortables in combination with the ng-drag-drop library to sort the list items that are being dragged, but it appears that nothing is happening when I try to use it. Does anyone know if angular-sortables is compatible with Angular ...

What could be causing my JavaScript code to malfunction, even though it appears to be coded correctly?

// JavaScript Document "use strict"; $(window).scroll(function(){ if($(window).scroll() > 100){ $("#scrollTop").fadeIn(); } }); $(window).scroll(function(){ if($(window).scroll() < 100){ $("#scrollTop").fadeOut(); } }); $(document).ready(function() ...

Server not displaying React Components

Just starting out with React and I'm puzzled why my components won't load. I may have some outdated code lingering around that's causing the issue. Repository URL: https://github.com/04lex/alex-workspace/tree/1da077943681bccba1876165cfd299b ...

Error when using the vue-masonry package in laramix

I am currently working on a Laravel 5.4 project. I have encountered an issue with the vue-masonry package (https://github.com/shershen08/vue-masonry) showing the following error: [Vue warn]: Failed to resolve directive: masonry [Vue warn]: Failed to res ...

Setting a fixed data value within a div for subsequent retrieval through a function

I found a helpful example that demonstrates how to convert numbers into words. You can check it out here. The function for converting numbers into words is implemented in the following HTML code: <input type="text" name="number" placeholder="Number OR ...