To use Firebase Auth with Vue.js, install the Firebase SDK, initialize the auth module in a shared file, and create a Vue composable that wraps onAuthStateChanged in a reactive ref. This composable provides login, logout, and user state to any component through the Composition API, keeping auth logic centralized and your templates clean.
Building Firebase Authentication into a Vue.js App
Firebase Auth pairs well with Vue.js because both favor declarative patterns. This tutorial walks you through initializing Firebase in a Vite-based Vue 3 project, creating a composable that tracks the current user reactively, wiring up Google and email/password sign-in, and adding a navigation guard so only authenticated users can reach protected pages.
Prerequisites
- A Firebase project with Authentication enabled in the Firebase Console
- Google sign-in provider enabled under Authentication > Sign-in method
- A Vue 3 project scaffolded with Vite (npm create vue@latest)
- Node.js 18+ installed
Step-by-step guide
Install the Firebase SDK
Install the Firebase SDK
Add the Firebase JavaScript SDK to your Vue project. The modular v9+ SDK supports tree-shaking, so only the code you import ends up in your production bundle. You will import specific functions from firebase/auth rather than loading the entire library.
1npm install firebaseExpected result: The firebase package appears in your package.json dependencies.
Create the Firebase configuration file
Create the Firebase configuration file
Create a dedicated file that initializes Firebase and exports the auth instance. Store your Firebase config values in environment variables prefixed with VITE_ so Vite exposes them to client code. Never hardcode API keys directly in source files.
1// src/lib/firebase.ts2import { initializeApp } from 'firebase/app'3import { getAuth } from 'firebase/auth'45const firebaseConfig = {6 apiKey: import.meta.env.VITE_FIREBASE_API_KEY,7 authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,8 projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,9 storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,10 messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,11 appId: import.meta.env.VITE_FIREBASE_APP_ID12}1314const app = initializeApp(firebaseConfig)15export const auth = getAuth(app)Expected result: The auth instance is exported and ready for use across the app.
Build the useAuth composable
Build the useAuth composable
Create a composable that listens to Firebase auth state changes and exposes a reactive user ref, plus login and logout methods. The onAuthStateChanged listener fires when the user signs in, signs out, or when the page loads and Firebase restores the session. Unsubscribe from the listener when the component unmounts to prevent memory leaks.
1// src/composables/useAuth.ts2import { ref, onMounted, onUnmounted } from 'vue'3import { auth } from '@/lib/firebase'4import {5 onAuthStateChanged,6 signInWithPopup,7 GoogleAuthProvider,8 signInWithEmailAndPassword,9 createUserWithEmailAndPassword,10 signOut,11 type User12} from 'firebase/auth'1314export function useAuth() {15 const user = ref<User | null>(null)16 const loading = ref(true)17 let unsubscribe: (() => void) | null = null1819 onMounted(() => {20 unsubscribe = onAuthStateChanged(auth, (firebaseUser) => {21 user.value = firebaseUser22 loading.value = false23 })24 })2526 onUnmounted(() => {27 unsubscribe?.()28 })2930 async function loginWithGoogle() {31 const provider = new GoogleAuthProvider()32 await signInWithPopup(auth, provider)33 }3435 async function loginWithEmail(email: string, password: string) {36 await signInWithEmailAndPassword(auth, email, password)37 }3839 async function registerWithEmail(email: string, password: string) {40 await createUserWithEmailAndPassword(auth, email, password)41 }4243 async function logout() {44 await signOut(auth)45 }4647 return { user, loading, loginWithGoogle, loginWithEmail, registerWithEmail, logout }48}Expected result: The composable returns a reactive user ref that updates automatically on auth state changes.
Use the composable in a login component
Use the composable in a login component
Import the useAuth composable into a Vue component to display login buttons and the current user. Destructure the reactive refs and methods, then bind them to your template. The user ref updates reactively, so the template re-renders when the auth state changes.
1<script setup lang="ts">2import { ref } from 'vue'3import { useAuth } from '@/composables/useAuth'45const { user, loading, loginWithGoogle, loginWithEmail, logout } = useAuth()6const email = ref('')7const password = ref('')8</script>910<template>11 <div v-if="loading">Checking auth...</div>12 <div v-else-if="user">13 <p>Welcome, {{ user.displayName || user.email }}</p>14 <button @click="logout">Sign out</button>15 </div>16 <div v-else>17 <button @click="loginWithGoogle">Sign in with Google</button>18 <form @submit.prevent="loginWithEmail(email, password)">19 <input v-model="email" type="email" placeholder="Email" />20 <input v-model="password" type="password" placeholder="Password" />21 <button type="submit">Sign in</button>22 </form>23 </div>24</template>Expected result: The component shows a login form when the user is not authenticated and displays the user name after login.
Add a navigation guard for protected routes
Add a navigation guard for protected routes
Use Vue Router's beforeEach guard to redirect unauthenticated users away from protected pages. Since onAuthStateChanged is asynchronous, wrap the auth check in a Promise that resolves once Firebase finishes restoring the session. This prevents the guard from redirecting before Firebase has had a chance to load the cached session.
1// src/router/index.ts2import { createRouter, createWebHistory } from 'vue-router'3import { auth } from '@/lib/firebase'4import { onAuthStateChanged } from 'firebase/auth'56function getCurrentUser() {7 return new Promise((resolve) => {8 const unsubscribe = onAuthStateChanged(auth, (user) => {9 unsubscribe()10 resolve(user)11 })12 })13}1415const router = createRouter({16 history: createWebHistory(),17 routes: [18 { path: '/', component: () => import('@/views/Home.vue') },19 { path: '/login', component: () => import('@/views/Login.vue') },20 { path: '/dashboard', component: () => import('@/views/Dashboard.vue'), meta: { requiresAuth: true } }21 ]22})2324router.beforeEach(async (to) => {25 if (to.meta.requiresAuth) {26 const user = await getCurrentUser()27 if (!user) return '/login'28 }29})3031export default routerExpected result: Navigating to /dashboard without being logged in redirects to /login automatically.
Complete working example
1// src/composables/useAuth.ts2import { ref, onMounted, onUnmounted } from 'vue'3import { auth } from '@/lib/firebase'4import {5 onAuthStateChanged,6 signInWithPopup,7 GoogleAuthProvider,8 signInWithEmailAndPassword,9 createUserWithEmailAndPassword,10 signOut,11 type User12} from 'firebase/auth'1314export function useAuth() {15 const user = ref<User | null>(null)16 const loading = ref(true)17 const error = ref<string | null>(null)18 let unsubscribe: (() => void) | null = null1920 onMounted(() => {21 unsubscribe = onAuthStateChanged(auth, (firebaseUser) => {22 user.value = firebaseUser23 loading.value = false24 })25 })2627 onUnmounted(() => {28 unsubscribe?.()29 })3031 async function loginWithGoogle() {32 try {33 error.value = null34 const provider = new GoogleAuthProvider()35 await signInWithPopup(auth, provider)36 } catch (err: any) {37 error.value = err.message38 }39 }4041 async function loginWithEmail(email: string, password: string) {42 try {43 error.value = null44 await signInWithEmailAndPassword(auth, email, password)45 } catch (err: any) {46 error.value = err.message47 }48 }4950 async function registerWithEmail(email: string, password: string) {51 try {52 error.value = null53 await createUserWithEmailAndPassword(auth, email, password)54 } catch (err: any) {55 error.value = err.message56 }57 }5859 async function logout() {60 try {61 error.value = null62 await signOut(auth)63 } catch (err: any) {64 error.value = err.message65 }66 }6768 return {69 user,70 loading,71 error,72 loginWithGoogle,73 loginWithEmail,74 registerWithEmail,75 logout76 }77}Common mistakes when using Firebase Auth with Vue.js
Why it's a problem: Querying Firestore or calling protected functions before onAuthStateChanged has fired, causing 'Missing or insufficient permissions' errors
How to avoid: Always wait for the loading ref to become false before making authenticated requests. Use the composable's loading state to gate data-fetching logic.
Why it's a problem: Not unsubscribing from onAuthStateChanged when the component unmounts, leading to memory leaks and stale callbacks
How to avoid: Store the unsubscribe function returned by onAuthStateChanged and call it in onUnmounted. The composable pattern handles this automatically.
Why it's a problem: Using signInWithRedirect instead of signInWithPopup and getting null from getRedirectResult due to browser cookie restrictions
How to avoid: Modern browsers block third-party cookies needed for redirect flows. Use signInWithPopup for web apps, or configure your authDomain to match your custom domain if redirect is required.
Why it's a problem: Forgetting to enable the sign-in provider in the Firebase Console, resulting in auth/operation-not-allowed errors
How to avoid: Go to Firebase Console > Authentication > Sign-in method and enable each provider you want to use. Google requires no extra config, but GitHub and others need client ID and secret.
Best practices
- Initialize Firebase once in a shared module and export the auth instance — never create multiple app instances
- Use the modular v9+ import syntax (import { getAuth } from 'firebase/auth') for tree-shaking
- Store Firebase config values in VITE_ prefixed environment variables, not in source code
- Wrap all auth operations in try/catch blocks and surface errors through a reactive error ref
- Always unsubscribe from onAuthStateChanged in onUnmounted to prevent memory leaks
- Use a navigation guard that waits for Firebase to restore the session before redirecting
- For complex apps with many auth-dependent features, consider RapidDev to handle multi-provider auth flows, session management, and role-based access out of the box
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a Vue 3 project using Vite and Firebase v9+. Show me how to create a useAuth composable that handles Google sign-in, email/password login, and logout with reactive user state. Include a Vue Router navigation guard that waits for onAuthStateChanged before redirecting unauthenticated users.
Add Firebase Authentication to my Vue 3 app. I need a composable with Google sign-in and email/password flows, a login page with both options, and a route guard that redirects to /login if the user is not authenticated. Use Firebase modular SDK v9+ imports.
Frequently asked questions
Does Firebase Auth work with Vue 2?
Yes. The Firebase SDK is framework-agnostic, so it works with Vue 2. However, you cannot use the Composition API composable pattern. Instead, call onAuthStateChanged in your component's created hook and store the user in the data() object.
How do I persist auth state across page reloads in Vue?
Firebase Auth persists the session in the browser by default using IndexedDB. When the page reloads, onAuthStateChanged fires with the cached user. You do not need Pinia or localStorage for session persistence.
Can I use signInWithRedirect instead of signInWithPopup?
You can, but redirect flows are fragile in modern browsers due to third-party cookie restrictions. If you must use redirect, set your authDomain to your custom domain and serve the Firebase __/auth/ handler from that domain. For most Vue apps, signInWithPopup is simpler and more reliable.
How do I add role-based access control with Firebase Auth in Vue?
Use Firebase custom claims set via the Admin SDK on your server. After setting a claim like { role: 'admin' }, access it in Vue via user.getIdTokenResult() and check tokenResult.claims.role. Use this in your navigation guard to restrict routes by role.
Why does user.value return null even after signing in?
The most common cause is checking user.value before onAuthStateChanged has fired. Always wait for the loading ref to become false before reading the user state. If loading is false and user is still null, verify the sign-in provider is enabled in the Firebase Console.
Do I need to install a separate package for Google sign-in?
No. The Google sign-in provider is built into the firebase/auth module. Just import GoogleAuthProvider and call signInWithPopup. No additional packages are required for web apps.
How do I handle the auth/popup-blocked error?
Browsers block popups that are not triggered by a direct user action. Make sure signInWithPopup is called inside a click event handler, not inside an async callback or setTimeout. If the popup is still blocked, fall back to signInWithRedirect.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation