Vue 3 Firebase Authentication Tutorial with Google and Email Login

by Didin J. on Jun 23, 2025 Vue 3 Firebase Authentication Tutorial with Google and Email Login

Learn how to build Vue 3 Firebase authentication with Email/Password and Google login. Includes auth guards, Vuetify styling, and session persistence.

In this tutorial, you will learn how to implement user authentication in a Vue 3 application using Firebase Authentication. We’ll cover both Email/Password authentication and Google Sign-In, two of the most commonly used login methods in modern web applications.

By the end of this tutorial, you will have a fully functional Vue 3 app with:

  • Firebase Email/Password sign up and login

  • Google login integration

  • User session management

  • Route protection using Vue Router navigation guards

We’ll be using the Composition API, Vite for the development environment, and the latest Firebase JavaScript SDK (version 10 or higher). This tutorial is ideal for beginners who want to quickly and efficiently integrate authentication into their Vue apps.


Prerequisites

Before starting, make sure you have the following installed:

  • Node.js (v18+ recommended)

  • npm or yarn

  • Firebase account (console.firebase.google.com)



1. Create a Vue 3 Project with Vite

Open your terminal and run:

npm create vite@latest vue3-firebase-auth -- --template vue cd vue3-firebase-auth npm install

Then install the necessary dependencies:

npm install firebase vue-router@4


2. Initialize Firebase Project

  1. Go to Firebase Console

  2. Click "Add project" and follow the setup wizard

  3. Once your project is created, go to Build > Authentication, then:

    • Click "Get started."

    • Enable Email/Password and Google sign-in methods

Next, click the gear icon ⚙️ in the sidebar → Project settingsGeneral tab. Scroll down to your apps, choose Web, and register a new app. You’ll be given your Firebase config object like this:

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "your-app.firebaseapp.com",
  projectId: "your-app",
  storageBucket: "your-app.appspot.com",
  messagingSenderId: "XXXXXX",
  appId: "YOUR_APP_ID"
};

Copy this config—you’ll need it in the next step.



3. Set Up Firebase in Vue

Create a new file called firebase.js in the src folder:

import { initializeApp } from 'firebase/app'
import { getAuth, GoogleAuthProvider } from 'firebase/auth'

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "your-app.firebaseapp.com",
  projectId: "your-app",
  storageBucket: "your-app.appspot.com",
  messagingSenderId: "XXXXXX",
  appId: "YOUR_APP_ID"
}

// Initialize Firebase
const app = initializeApp(firebaseConfig)
const auth = getAuth(app)
const provider = new GoogleAuthProvider()

export { auth, provider }

>br />4. Create Authentication Pages

We’ll create two pages:

  • LoginView.vue – for login with email and Google

  • RegisterView.vue – for new user registration

Create a new folder src/views/ and add the following files:


📄 src/views/LoginView.vue

<template>
  <div class="login">
    <h2>Login</h2>
    <form @submit.prevent="loginWithEmail">
      <input v-model="email" type="email" placeholder="Email" required />
      <input v-model="password" type="password" placeholder="Password" required />
      <button type="submit">Login</button>
    </form>
    <button @click="loginWithGoogle">Login with Google</button>
    <p>
      Don't have an account?
      <router-link to="/register">Register</router-link>
    </p>
    <p v-if="error" class="error">{{ error }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { auth, provider } from '../firebase'
import { signInWithEmailAndPassword, signInWithPopup } from 'firebase/auth'
import { useRouter } from 'vue-router'

const email = ref('')
const password = ref('')
const error = ref('')
const router = useRouter()

const loginWithEmail = async () => {
  try {
    await signInWithEmailAndPassword(auth, email.value, password.value)
    router.push('/dashboard')
  } catch (err) {
    error.value = err.message
  }
}

const loginWithGoogle = async () => {
  try {
    await signInWithPopup(auth, provider)
    router.push('/dashboard')
  } catch (err) {
    error.value = err.message
  }
}
</script>

<style scoped>
.login {
  max-width: 400px;
  margin: auto;
}
.error {
  color: red;
}
</style>

📄 src/views/RegisterView.vue

<template>
  <div class="register">
    <h2>Register</h2>
    <form @submit.prevent="register">
      <input v-model="email" type="email" placeholder="Email" required />
      <input v-model="password" type="password" placeholder="Password" required />
      <button type="submit">Register</button>
    </form>
    <p>
      Already have an account?
      <router-link to="/login">Login</router-link>
    </p>
    <p v-if="error" class="error">{{ error }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { auth } from '../firebase'
import { createUserWithEmailAndPassword } from 'firebase/auth'
import { useRouter } from 'vue-router'

const email = ref('')
const password = ref('')
const error = ref('')
const router = useRouter()

const register = async () => {
  try {
    await createUserWithEmailAndPassword(auth, email.value, password.value)
    router.push('/dashboard')
  } catch (err) {
    error.value = err.message
  }
}
</script>

<style scoped>
.register {
  max-width: 400px;
  margin: auto;
}
.error {
  color: red;
}
</style>


5. Set up Routing with Vue Router

Now, let’s create routes and enable navigation between login, register, and dashboard.

📄 src/router.js

import { createRouter, createWebHistory } from 'vue-router'
import LoginView from './views/LoginView.vue'
import RegisterView from './views/RegisterView.vue'
import DashboardView from './views/DashboardView.vue'
import { auth } from './firebase'

const requireAuth = (to, from, next) => {
  const user = auth.currentUser
  if (user) {
    next()
  } else {
    next('/login')
  }
}

const routes = [
  { path: '/', redirect: '/login' },
  { path: '/login', component: LoginView },
  { path: '/register', component: RegisterView },
  {
    path: '/dashboard',
    component: DashboardView,
    beforeEnter: requireAuth
  }
]

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

export default router

📄 src/views/DashboardView.vue

<template>
  <div>
    <h2>Welcome, {{ user?.email }}</h2>
    <button @click="logout">Logout</button>
  </div>
</template>

<script setup>
import { auth } from '../firebase'
import { signOut } from 'firebase/auth'
import { useRouter } from 'vue-router'
import { onMounted, ref } from 'vue'

const user = ref(null)
const router = useRouter()

onMounted(() => {
  user.value = auth.currentUser
})

const logout = async () => {
  await signOut(auth)
  router.push('/login')
}
</script>


6. Register the Router in main.js

// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')

Update App.vue:

<template>
  <v-app>
    <v-main>
      <router-view />
    </v-main>
  </v-app>
</template>

<script setup>
// No logic needed here
</script>

<style>
/* Optional global styles */
body {
  margin: 0;
  font-family: Roboto, sans-serif;
}
</style>

< br />7. Auth State Persistence and Global User State

Let’s listen to Firebase’s auth state changes using onAuthStateChanged. We'll set this up in a global store-style pattern to keep track of the user and control routing.


🧠 Step 1: Create an Auth Store (src/stores/auth.js)

// src/stores/auth.js
import { ref } from 'vue'
import { onAuthStateChanged } from 'firebase/auth'
import { auth } from '../firebase'

const currentUser = ref(null)
const isAuthReady = ref(false)

onAuthStateChanged(auth, (user) => {
  currentUser.value = user
  isAuthReady.value = true
})

export function useAuth() {
  return { currentUser, isAuthReady }
}

This will:

  • Keep track of the current user

  • Ensure we don’t try to render protected pages before the auth state is determined


🧠 Step 2: Guard Navigation with Auth Store

Update your route guard in router.js to wait for the auth state to initialize.

// src/router.js
import { createRouter, createWebHistory } from 'vue-router'
import LoginView from './views/LoginView.vue'
import RegisterView from './views/RegisterView.vue'
import DashboardView from './views/DashboardView.vue'
import { useAuth } from './stores/auth'

const requireAuth = (to, from, next) => {
  const { currentUser, isAuthReady } = useAuth()

  const waitForAuth = () => {
    if (isAuthReady.value) {
      currentUser.value ? next() : next('/login')
    } else {
      setTimeout(waitForAuth, 50)
    }
  }

  waitForAuth()
}

const routes = [
  { path: '/', redirect: '/login' },
  { path: '/login', component: LoginView },
  { path: '/register', component: RegisterView },
  {
    path: '/dashboard',
    component: DashboardView,
    beforeEnter: requireAuth
  }
]

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

export default router

🧠 Step 3: Use the Auth Store in Components (Optional)

Instead of calling auth.currentUser directly, you can now import and use the global user state:

import { useAuth } from '../stores/auth'

const { currentUser } = useAuth()

✅ Firebase Default Persistence Mode

By default, Firebase uses local persistence (i.e., user stays signed in across tabs and sessions). If you want to explicitly set it, update it firebase.js like this:

import { initializeApp } from 'firebase/app'
import {
  getAuth,
  GoogleAuthProvider,
  setPersistence,
  browserLocalPersistence
} from 'firebase/auth'

const app = initializeApp(firebaseConfig)
const auth = getAuth(app)
const provider = new GoogleAuthProvider()

// Set local persistence explicitly
setPersistence(auth, browserLocalPersistence)

export { auth, provider }

That’s it! Now your app will:

  • Persist login state across refreshes

  • Wait for Firebase to determine the auth state before routing

  • Allow global access to the current user



8. Add Vuetify to Style the App

📦 Step 1: Install Vuetify 3

Vuetify 3 works seamlessly with Vite and Vue 3.

npm install vuetify@^3.5 @mdi/font sass sass-loader -D

Then update your main.js:

// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

// Vuetify setup
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import { aliases, mdi } from 'vuetify/iconsets/mdi'
import '@mdi/font/css/materialdesignicons.css'

const vuetify = createVuetify({
  components,
  directives,
  icons: {
    defaultSet: 'mdi',
    aliases,
    sets: { mdi },
  },
})

createApp(App).use(router).use(vuetify).mount('#app')


9. Update Auth Pages with Vuetify Components

✨ Replace LoginView.vue with Vuetify UI

<template>
  <v-container class="fill-height" fluid>
    <v-row justify="center" align="center">
      <v-col cols="12" sm="8" md="4">
        <v-card>
          <v-card-title class="text-h5 text-center">Login</v-card-title>
          <v-card-text>
            <v-form @submit.prevent="loginWithEmail">
              <v-text-field
                v-model="email"
                label="Email"
                type="email"
                required
              ></v-text-field>
              <v-text-field
                v-model="password"
                label="Password"
                type="password"
                required
              ></v-text-field>
              <v-btn type="submit" color="primary" block>Login</v-btn>
            </v-form>

            <v-divider class="my-4"></v-divider>

            <v-btn color="red darken-1" block @click="loginWithGoogle">
              <v-icon start>mdi-google</v-icon> Login with Google
            </v-btn>

            <v-alert type="error" v-if="error" class="mt-4">{{ error }}</v-alert>
          </v-card-text>
          <v-card-actions class="justify-center">
            <router-link to="/register">Don't have an account? Register</router-link>
          </v-card-actions>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

<script setup>
import { ref } from 'vue'
import { auth, provider } from '../firebase'
import { signInWithEmailAndPassword, signInWithPopup } from 'firebase/auth'
import { useRouter } from 'vue-router'

const email = ref('')
const password = ref('')
const error = ref('')
const router = useRouter()

const loginWithEmail = async () => {
  try {
    await signInWithEmailAndPassword(auth, email.value, password.value)
    router.push('/dashboard')
  } catch (err) {
    error.value = err.message
  }
}

const loginWithGoogle = async () => {
  try {
    await signInWithPopup(auth, provider)
    router.push('/dashboard')
  } catch (err) {
    error.value = err.message
  }
}
</script>

✨ Similarly, update RegisterView.vue with Vuetify

<template>
  <v-container class="fill-height" fluid>
    <v-row justify="center" align="center">
      <v-col cols="12" sm="8" md="4">
        <v-card>
          <v-card-title class="text-h5 text-center">Register</v-card-title>
          <v-card-text>
            <v-form @submit.prevent="register">
              <v-text-field
                v-model="email"
                label="Email"
                type="email"
                required
              ></v-text-field>
              <v-text-field
                v-model="password"
                label="Password"
                type="password"
                required
              ></v-text-field>
              <v-btn type="submit" color="primary" block>Register</v-btn>
            </v-form>

            <v-alert type="error" v-if="error" class="mt-4">{{ error }}</v-alert>
          </v-card-text>
          <v-card-actions class="justify-center">
            <router-link to="/login">Already have an account? Login</router-link>
          </v-card-actions>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

<script setup>
import { ref } from 'vue'
import { auth } from '../firebase'
import { createUserWithEmailAndPassword } from 'firebase/auth'
import { useRouter } from 'vue-router'

const email = ref('')
const password = ref('')
const error = ref('')
const router = useRouter()

const register = async () => {
  try {
    await createUserWithEmailAndPassword(auth, email.value, password.value)
    router.push('/dashboard')
  } catch (err) {
    error.value = err.message
  }
}
</script>

🧑‍💼 Optional: Style DashboardView.vue with Vuetify

<template>
  <v-container>
    <v-card class="pa-4 text-center">
      <v-card-title>Welcome, {{ user?.email }}</v-card-title>
      <v-card-text>
        <v-btn color="primary" @click="logout">Logout</v-btn>
      </v-card-text>
    </v-card>
  </v-container>
</template>

<script setup>
import { auth } from '../firebase'
import { signOut } from 'firebase/auth'
import { useRouter } from 'vue-router'
import { onMounted, ref } from 'vue'

const user = ref(null)
const router = useRouter()

onMounted(() => {
  user.value = auth.currentUser
})

const logout = async () => {
  await signOut(auth)
  router.push('/login')
}
</script>

Run the Vue 3 app:

npm run dev

Now, the Vue 3 app looks like this:

Vue 3 Firebase Authentication Tutorial with Google and Email Login - login

Vue 3 Firebase Authentication Tutorial with Google and Email Login - register

Vue 3 Firebase Authentication Tutorial with Google and Email Login - dashboard

Vue 3 Firebase Authentication Tutorial with Google and Email Login - google signin



10. Conclusion

In this tutorial, you've learned how to build a modern authentication system in Vue 3 using Firebase. We covered how to:

  • Set up a Vue 3 project with Vite

  • Configure Firebase for Email/Password and Google Sign-In

  • Create login, registration, and protected dashboard pages

  • Manage authentication state and session persistence

  • Style the UI using Vuetify 3 components

With these fundamentals, you can now expand this app further by adding profile pages, email verification, password reset, or even Firestore for user data.

You can get the full source code on our GitHub.

That's just the basics. If you need more deep learning about Vue, you can take the following cheap course:

Happy coding! 🔥