The Complete Guide to User Authentication with Vue.js and Firebase

Building user authentication into a web app can seem daunting at first, but modern tools like Vue.js and Firebase make the process straightforward. In this in-depth tutorial, we‘ll walk through how to add a complete sign-up and sign-in flow to a Vue app, and protect certain pages from unauthenticated access.

We‘ll also look at some more advanced topics like handling invalid URLs with vue-router‘s catch-all functionality. By the end, you‘ll have a solid foundation for adding auth to your own Vue projects. Let‘s jump in!

Why Vue.js and Firebase?

Vue.js is a popular progressive JavaScript framework for building interactive web UIs. Its component-based architecture and declarative rendering make it easy to reason about your interface.

Firebase is a comprehensive app development platform with a suite of tools for building scalable, secure apps. Critically for us, it includes simple, drop-in user authentication.

By combining Vue for the frontend and Firebase for the backend auth, we get a powerful yet beginner-friendly stack for adding sign-up, sign-in, and protected routes to an app. Vue‘s reactivity system integrates nicely with Firebase‘s real-time auth state.

Project Setup

We‘ll start by spinning up a new Vue project using the official vue-cli. Make sure you have Node.js installed, then run:

npm install -g @vue/cli
vue create my-auth-app
cd my-auth-app

Choose the default preset for a basic Babel + Webpack setup. Once the project is created, we‘ll also install the necessary dependencies:

npm install firebase vue-router

We‘ll be using the official Vue Router plugin for navigation and route protection, and the Firebase JS SDK to interact with the backend.

Configuring Firebase

Next, let‘s set up a new Firebase project to handle the authentication. Head over to the Firebase Console and click "Add project". Give it a name and accept the terms.

Once the project is initialized, go to the Authentication section and enable the "Email/Password" sign-in method. Under the Users tab, add a test user with an email and password of your choosing.

Finally, grab the Firebase config object by clicking the gear icon and selecting "Project settings". It should look something like this:

const firebaseConfig = {
  apiKey: "AIxaSyD12tpsR29-IDIKZgLEwOASuuMwXFGPP78",
  authDomain: "my-auth-app-demo.firebaseapp.com",
  // ...
};

Initializing Firebase in Vue

Back in the Vue app, create a src/firebase.js file and add:

import { initializeApp } from ‘firebase/app‘;
import { getAuth } from ‘firebase/auth‘;

const firebaseConfig = {
  // Your config object from the Firebase Console
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

export { auth };

This initializes the Firebase app with your unique configuration, and exports the authentication service which we‘ll use in the Vue components.

Setting Up Vue Router

Vue Router will handle switching between pages in our SPA, and protect authenticated routes. Create a src/router/index.js file with:

import { createRouter, createWebHistory } from ‘vue-router‘;
import { auth } from ‘../firebase‘;

import Home from ‘../views/Home.vue‘;
import Login from ‘../views/Login.vue‘;
import SignUp from ‘../views/SignUp.vue‘;
import Dashboard from ‘../views/Dashboard.vue‘;

const routes = [
  {
    path: ‘/‘,
    component: Home
  },
  {
    path: ‘/login‘,
    component: Login
  },
  {
    path: ‘/signup‘,
    component: SignUp  
  },
  {
    path: ‘/dashboard‘,
    component: Dashboard,
    meta: {
      requiresAuth: true
    }
  },
  {
    // Catch-all for invalid routes
    path: ‘/:pathMatch(.*)*‘,
    component: Home
  }
];

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

router.beforeEach((to, from, next) => {
  const requiresAuth = to.matched.some(x => x.meta.requiresAuth);

  if (requiresAuth && !auth.currentUser) {
    next(‘/login‘);
  } else {
    next();
  }
});

export default router;

Here we‘re defining routes for the home page, login, signup, and a protected dashboard. Note the meta field on the dashboard route which will require authentication.

The catch-all route at the bottom uses a named parameter pathMatch and a greedy custom regex (.*) to match anything not explicitly defined.

Importantly, the router.beforeEach navigation guard runs before each route change. It checks if the route requires authentication (based on the meta field) and redirects to login if the user isn‘t authenticated.

We use auth.currentUser to reactively check if the user is signed in. This will automatically update whenever the auth state changes.

Creating the Vue Components

With the router in place, let‘s fill in the view components, starting with Home:

<!-- src/views/Home.vue -->
<template>
  <div>

    <p>
      <router-link to="/login">Login</router-link> or 
      <router-link to="/signup">Sign Up</router-link> to get started.
    </p>
  </div>
</template>

Simple enough – just a splash page with links to login or signup. Let‘s implement those views next, starting with SignUp:

<!-- src/views/SignUp.vue -->
<template>
  <form @submit.prevent="signUp">
    <h2>Sign Up</h2>

    <label for="email">Email:</label>
    <input type="email" name="email" v-model="email">

    <label for="password">Password:</label>
    <input type="password" name="password" v-model="password">

    <button type="submit">Sign Up</button>
  </form>
</template>

<script>
import { ref } from ‘vue‘;
import { auth } from ‘../firebase‘;
import { createUserWithEmailAndPassword } from ‘firebase/auth‘;

export default {
  setup() {
    const email = ref(‘‘);
    const password = ref(‘‘);

    const signUp = async () => {
      try {
        await createUserWithEmailAndPassword(auth, email.value, password.value);
      } catch (error) {
        alert(error.message);
      }
    };

    return { email, password, signUp };
  }
}
</script>

This renders a sign-up form with email and password fields, bound to reactive refs using v-model. When the form is submitted, the signUp function is called.

This uses the Firebase createUserWithEmailAndPassword function to attempt to create a new user. If it succeeds, the user will automatically be signed in and redirected via the router guard. If it fails, we display the error message.

The Login component is nearly identical, but uses signInWithEmailAndPassword instead:

<!-- src/views/Login.vue -->
<template>
  <form @submit.prevent="signIn">
    <h2>Login</h2>

    <label for="email">Email:</label>
    <input type="email" name="email" v-model="email">

    <label for="password">Password:</label>
    <input type="password" name="password" v-model="password">

    <button type="submit">Log In</button>
  </form>
</template>

<script>
import { ref } from ‘vue‘;  
import { auth } from ‘../firebase‘;
import { signInWithEmailAndPassword } from ‘firebase/auth‘;

export default {
  setup() {
    const email = ref(‘‘);
    const password = ref(‘‘);

    const signIn = async () => {
      try {
        await signInWithEmailAndPassword(auth, email.value, password.value);
      } catch (error) {
        alert(error.message);
      }
    };

    return { email, password, signIn };
  }
}
</script>

Finally, let‘s create the protected Dashboard view:

<!-- src/views/Dashboard.vue -->
<template>
  <div>

    <p>Congrats on logging in! This page is top secret.</p>
    <button @click="signOut">Sign Out</button>
  </div>  
</template>

<script>
import { auth } from ‘../firebase‘;
import { signOut } from ‘firebase/auth‘;

export default {
  setup() {
    const signOut = async () => {
      try {
        await signOut(auth);
      } catch (error) {
        alert(error.message);
      }
    };

    return { signOut };
  }
}
</script>

This is a basic stub showing a secret message and sign out button. When clicked, the signOut function will sign the user out of Firebase and redirect them via the router guard.

Handling Auth State

Our final consideration is how to reactively track the current user‘s authentication state across the entire app. While the router guards prevent access to protected routes, it‘d be nice to show/hide the login and sign out buttons based on auth state.

Since the auth.currentUser observable updates automatically, we can create a small composable to share that state with any component:

// src/composables/useAuth.js 
import { ref } from ‘vue‘;
import { auth } from ‘../firebase‘;

const user = ref(auth.currentUser);

auth.onAuthStateChanged((_user) => {
  user.value = _user;
});

const isLoggedIn = () => {
  return user.value !== null;  
};

export default function useAuth() {
  return { user, isLoggedIn };
}

This exports a useAuth composable which returns the current user and an isLoggedIn helper. We can use this in any component like:

<template>
  <div>
    <div v-if="isLoggedIn()">
      Signed in as: {{ user.email }}
      <button @click="signOut">Sign Out</button>
    </div>
    <div v-else>
      <router-link to="/login">Log In</router-link>
    </div>
  </div>
</template>

<script>
import useAuth from ‘./composables/useAuth‘;
import { auth } from ‘./firebase‘;
import { signOut } from ‘firebase/auth‘;

export default {
  setup() {
    const { user, isLoggedIn } = useAuth();

    const signOut = async () => {
      await signOut(auth);
    };

    return { user, isLoggedIn, signOut }; 
  }
}
</script>

This will reactively show the user‘s email and sign out button if logged in, or a login link otherwise.

Deploying to Firebase Hosting

As a final step, let‘s deploy our finished app to Firebase Hosting for the world to see. Run npm run build to create a production bundle in the dist directory.

Then, install the Firebase CLI:

npm install -g firebase-tools
firebase login

Authenticate with your Google account, then initialize a new Firebase project in the app directory:

firebase init hosting

Select the project you created earlier in the Firebase console. When prompted, use dist as the public directory and configure as a single-page app.

Finally, deploy the app:

firebase deploy

After a minute, you should see a "Hosting URL" that you can visit to see the live app! Our authentication flow is now complete.

Next Steps

We‘ve covered the basics of adding authentication to a Vue 3 app with Firebase, including protecting routes, handling sign-up and sign-in, and gracefully catching invalid URLs.

Some additional features to consider adding:

  • Polishing the UI with a component library like Vuetify
  • Handling email verification and password resets
  • Storing additional user profile data in Firestore
  • Expanding the dashboard with data fetching and editing

I hope this guide has given you a solid foundation for adding auth to your own Vue projects. The complete source code is available on GitHub. Happy coding!

Similar Posts