Skip to content

Vue

Authentication with Firebase and VueJS

Build Firebase authentication in Vue with Pinia, GitHub login, session-aware route middleware, and protected authenticated and guest pages.

6 min read

I've been developing VueJS and Firebase focused mini projects lately. After making a simple Chat Application with Firebase, I wanted to prepare a boilerplate for myself that includes Authentication and can also do middleware operations, and I made a mini gallery application by including the Firebase Storage feature. In this article, I will talk about how we can perform Firebase and Auth transactions with Pinia.

Firebase is a platform developed by Google for building mobile and web applications. We can include the features that we want to use very quickly in many subjects in your project.

The document is sufficient. I used GitHub Provider as Authentication, you can integrate different methods into your system if you wish. I used Pinia as state management in the auth part.

Firebase configuration

First of all, we do the Firebase integration,

// plugins/firebase.js
import {initializeApp} from "firebase/app";
import { getAuth }from "firebase/auth";

const firebaseConfig = initializeApp({
    apiKey: "",
    authDomain: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
});

const auth = getAuth(firebaseConfig);

export {auth}

The firebaseConfig variable here contains the API Keys we received from Firebase. We then pass these variables as parameters to the getAuth function.

Router and middleware setup

Now let's do the Auth operations. Since we will create middleware in the router section, we create a file called 'router.js' under the 'plugins' folder. Then we register it to the project in the main.js file.

// plugins/router.js
import {createRouter, createWebHistory} from 'vue-router';

const routes: [];

const router = createRouter({
    history: createWebHistory(),
    routes: routes,
});

export default router;

In the function we created below, if no middleware is defined for the relevant route, we continue the request with the next() function without any action. If middleware is defined, we run the relevant middleware function.

// plugins/router.js
function nextFactory(context, middleware, index) {
    const subsequentMiddleware = middleware[index];
    if (!subsequentMiddleware) return context.next;
    return (...parameters) => {
        context.next(...parameters);
        const nextMiddleware = nextFactory(context, middleware, index + 1);
        subsequentMiddleware({...context, next: nextMiddleware});
    };
}

In the function below, we register our middlewares with the beforeEach function.

// plugins/router.js
router.beforeEach((to, from, next) => {
    if (to.meta.middleware) {
        const middleware = Array.isArray(to.meta.middleware) ? to.meta.middleware : [to.meta.middleware];
        const context = {from, next, router, to};
        const nextMiddleware = nextFactory(context, middleware, 1);
        
        return middleware[0]({...context, next: nextMiddleware});
    }
    return next();
});

Pinia authentication store

Now it's time for state management with Pinia. We create a file named authStore.js under the stores folder. In this file, we create a store using the defineStore function. In this store, we define variables with state, functions that we can read the values of variables with getters, and functions that we can change the values of variables and perform data operations with actions.

import { defineStore } from 'pinia'
import {
    onAuthStateChanged,
    GithubAuthProvider,
    signInWithPopup,
    signOut,
} from "firebase/auth";
import {auth} from "@/plugins/firebase";
import router from "@/plugins/router.js";

export const useAuthStore = defineStore('authStore', {
    state: () => ({
        userData: null,
        loadingUser: false,
        loadingSession: false,
    }),
    actions: {
        async login() {
            this.loadingUser = true;
            try {
                const provider = new GithubAuthProvider();
                const { user } = await signInWithPopup(auth, provider);
                this.userData = user;
                await router.push("/");
            }catch (e) {
                console.log(e)
            }finally {
                this.loadingUser = false;
            }
        },
        async logout() {
            try {
                await signOut(auth);
                this.userData = null;
                await router.push("/login");
            }catch (e) {
                console.log(e)
            }
        },
        async checkSession() {
            return new Promise((resolve, reject) => {
                const unsubscribe = onAuthStateChanged(
                    auth,
                    (user) => {
                        if (user) {
                            this.userData = user;
                        } else {
                            this.userData = null;
                        }
                        resolve(user);
                    },
                    (e) => reject(e)
                );
                unsubscribe();
            });
        }
    }
})

Here we are doing the login with Github in the login function. In the logout function, we log out with Firebase Auth. In the checkSession function, we check the session status of the user with Firebase Auth with the onAuthStateChanced function.

Using the store in pages

Then we import and use this store wherever we want to use it. For example, when we want to use it on a page,

import { useAuthStore } from '@/stores/authStore'

const authStore = useAuthStore()

We import as . In this way, we can access the variables and functions in the store with the authStore variable.

Now for the pages. Under the pages folder, we create two files named Login.vue and Home.vue. In these files, we perform our operations by using the variables and functions in the store within the setup function.

<script setup>
// pages/Login.vue
import { useAuthStore } from '@/stores/authStore'

const authStore = useAuthStore()

const login = () => {
  authStore.login()
}
</script>

<template>
    <div>
        <h1>Login</h1>
        <button @click="login">Login</button>
    </div>
</template>

Protecting authenticated routes

It's time for middleware. We create a file named auth.js under the middleware folder. In this file, we check the session status of the user with the checkSession function that we created with useAuthStore. If the user is not logged in, we redirect to the login page.

// middleware/auth.js
import {useAuthStore} from "@/stores/authStore.js";

export default async function auth({next, router}) {
    const userStore = useAuthStore();
    userStore.loadingSession = true;
    const user = await userStore.checkSession();
    if (user) {
        next();
    } else {
        next("/login");
    }
    userStore.loadingSession = false;
}

Now for the router.js file. In this file, we add the auth middleware that we created as middleware to the meta part.

// plugins/router.js
import auth from "@/middleware/auth.js";

const routes = [
    {
        path: "/",
        name: "Home",
        component: () => import("@/pages/Home.vue"),
        meta: {
            middleware: auth,
        },
    },
    {
        path: "/login",
        name: "Login",
        component: () => import("@/pages/Login.vue"),
    },
];

Protecting guest routes

Now let's do the reverse of this process. In other words, if the Login.vue page is logged in, it cannot be accessed. For this, we create a middleware again. We create a file named guest.js under the middleware folder. In this file, we check the session status of the user with the checkSession function that we created with useAuthStore. If the user is logged in, we are redirected to the 'Home' page.

// middleware/guest.js
import {useAuthStore} from "@/stores/authStore.js";

export default async function authCheck({next, router}) {
    const userStore = useAuthStore();
    userStore.loadingSession = true;
    const user = await userStore.checkSession();
    if (user) {
        next("/");
    } else {
        next();
    }
    userStore.loadingSession = false;
}

As you can see, we do the opposite. Now for the router.js file. In this file, we add the guest middleware that we created as middleware to the meta part.

// plugins/router.js
import auth from "@/middleware/auth.js";
import guest from "@/middleware/guest.js";

const routes = [
    {
        path: "/",
        name: "Home",
        component: () => import("@/pages/Home.vue"),
        meta: {
            middleware: auth,
        },
    },
    {
        path: "/login",
        name: "Login",
        component: () => import("@/pages/Login.vue"),
        meta: {
            middleware: guest,
        },
    },
];

Thus, we have done the session management operations using the Firebase Auth library in its simplest form, you can view the project by clicking the link below.

That's it for now, see you in the next post. 👋🏼

Project Link

Share this article

Vue2 min read

Pinia vs Vuex

Compare Pinia and Vuex through simple state management examples, API differences, and practical guidance for choosing a store for your Vue application.

  • Vue
  • Pinia
  • Vuex
PHP3 min read

How do we sort arrays in PHP?

Learn how PHP’s sort, rsort, asort, arsort, ksort, and krsort functions order indexed and associative arrays by values or keys.

  • PHP
  • Arrays