# How to Check If an User Is Logged In with Firebase

- Tool: Firebase
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Firebase (all plans), firebase 10.x+, any JavaScript/TypeScript framework
- Last updated: March 2026

## TL;DR

Check if a user is logged in with Firebase using onAuthStateChanged(), which fires a callback whenever the auth state changes. The callback receives the user object when signed in or null when signed out. Avoid using auth.currentUser directly on page load because it is null until Firebase finishes initializing. Wrap your auth check in a Promise or use a React hook with useEffect to properly handle the async initialization.

## Detecting Authentication State in Firebase with onAuthStateChanged

Firebase Auth is asynchronous — when your app loads, there is a brief period where Firebase checks if a user session exists. During this time, auth.currentUser is null even if the user is logged in. The correct pattern is onAuthStateChanged(), which fires once when auth initializes and again on every sign-in or sign-out. This tutorial shows the correct approach for vanilla JavaScript, React, and Next.js projects.

## Before you start

- A Firebase project with Authentication enabled (any provider)
- Firebase SDK installed and initialized in your project
- The getAuth instance exported from your firebase.ts module
- At least one auth provider enabled (email/password, Google, etc.)

## Step-by-step guide

### 1. Understand why auth.currentUser can be null on page load

When your app first loads, Firebase checks for an existing user session in the browser's IndexedDB or localStorage. This check is asynchronous and takes a moment to complete. During this time, auth.currentUser returns null even if the user has an active session. Using currentUser directly causes a common bug where authenticated users briefly see a logged-out state.

```
import { auth } from "@/lib/firebase";

// BAD — currentUser is null during initialization
const user = auth.currentUser;
console.log(user); // null on page load, even if user is signed in!

// This causes the logged-out UI to flash before the user appears
```

**Expected result:** You understand that auth.currentUser is unreliable on initial load and should not be used for auth checks.

### 2. Use onAuthStateChanged to detect login state

onAuthStateChanged registers a listener that fires immediately with the current auth state (once Firebase finishes initializing) and then fires again every time the user signs in or out. The callback receives a User object when signed in or null when signed out. This is the correct and recommended way to check authentication.

```
import { auth } from "@/lib/firebase";
import { onAuthStateChanged } from "firebase/auth";

onAuthStateChanged(auth, (user) => {
  if (user) {
    // User is signed in
    console.log("Logged in as:", user.uid);
    console.log("Email:", user.email);
    console.log("Display name:", user.displayName);
  } else {
    // User is signed out
    console.log("Not logged in");
  }
});
```

**Expected result:** The callback fires with the user object if signed in, or null if signed out.

### 3. Create a Promise-based auth check for one-time use

Sometimes you need a one-time auth check (e.g., before making an API call) rather than a continuous listener. Wrap onAuthStateChanged in a Promise that resolves with the first auth state. This is useful in utility functions where you cannot use a listener pattern.

```
import { auth } from "@/lib/firebase";
import { onAuthStateChanged, User } from "firebase/auth";

function getCurrentUser(): Promise<User | null> {
  return new Promise((resolve) => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      unsubscribe(); // Stop listening after first result
      resolve(user);
    });
  });
}

// Usage:
async function fetchProtectedData() {
  const user = await getCurrentUser();
  if (!user) {
    throw new Error("Not authenticated");
  }
  // Proceed with the authenticated user
  const token = await user.getIdToken();
  // Use token for API calls...
}
```

**Expected result:** The getCurrentUser function resolves with the user object or null after Firebase initialization completes.

### 4. Build a React useAuth hook

In React, create a custom hook that wraps onAuthStateChanged and returns the current user and loading state. Use useEffect to subscribe on mount and unsubscribe on unmount. The loading state is critical — it lets you show a loading spinner instead of flashing the wrong UI while Firebase initializes.

```
// src/hooks/useAuth.ts
import { useState, useEffect } from "react";
import { auth } from "@/lib/firebase";
import { onAuthStateChanged, User } from "firebase/auth";

export function useAuth() {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => {
      setUser(firebaseUser);
      setLoading(false);
    });

    return () => unsubscribe(); // Cleanup on unmount
  }, []);

  return { user, loading, isAuthenticated: !!user };
}

// Usage in a component:
// const { user, loading, isAuthenticated } = useAuth();
// if (loading) return <Spinner />;
// if (!isAuthenticated) return <LoginPage />;
```

**Expected result:** The useAuth hook provides user, loading, and isAuthenticated values that update in real-time.

### 5. Protect routes based on auth state

Create a wrapper component that checks authentication before rendering protected content. Show a loading indicator while Firebase initializes, redirect to login if not authenticated, and render the protected content only for authenticated users.

```
// src/components/ProtectedRoute.tsx
import { useAuth } from "@/hooks/useAuth";
import { Navigate } from "react-router-dom";

export function ProtectedRoute({
  children,
}: {
  children: React.ReactNode;
}) {
  const { user, loading } = useAuth();

  if (loading) {
    return <div className="flex justify-center p-8">Loading...</div>;
  }

  if (!user) {
    return <Navigate to="/login" replace />;
  }

  return <>{children}</>;
}

// Usage in your router:
// <Route path="/dashboard" element={
//   <ProtectedRoute><Dashboard /></ProtectedRoute>
// } />
```

**Expected result:** Protected routes show a loading state during auth initialization, redirect unauthenticated users to login, and render content for authenticated users.

## Complete code example

File: `src/hooks/useAuth.ts`

```typescript
import { useState, useEffect, useCallback } from "react";
import { auth } from "@/lib/firebase";
import {
  onAuthStateChanged,
  signOut as firebaseSignOut,
  User,
} from "firebase/auth";

export function useAuth() {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => {
      setUser(firebaseUser);
      setLoading(false);
    });

    return () => unsubscribe();
  }, []);

  const signOut = useCallback(async () => {
    await firebaseSignOut(auth);
  }, []);

  return {
    user,
    loading,
    isAuthenticated: !!user,
    signOut,
  };
}

// One-time auth check for utility functions
export function getCurrentUser(): Promise<User | null> {
  return new Promise((resolve) => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      unsubscribe();
      resolve(user);
    });
  });
}
```

## Common mistakes

- **Using auth.currentUser directly on page load to check if the user is logged in** — undefined Fix: auth.currentUser is null until Firebase finishes initializing, which can take a moment. Always use onAuthStateChanged() to wait for the auth state to resolve before making decisions.
- **Not showing a loading state while Firebase auth initializes, causing a flash of the login page for authenticated users** — undefined Fix: Track a loading boolean that starts as true and flips to false inside the onAuthStateChanged callback. Show a spinner or skeleton while loading is true.
- **Forgetting to unsubscribe from onAuthStateChanged when a React component unmounts** — undefined Fix: Store the unsubscribe function returned by onAuthStateChanged and call it in the useEffect cleanup: return () => unsubscribe().

## Best practices

- Always use onAuthStateChanged instead of auth.currentUser for reliable auth state detection
- Track a loading state to prevent UI flashes while Firebase initializes the auth session
- Create a reusable useAuth hook to centralize auth state management in React apps
- Unsubscribe from auth listeners when components unmount to prevent memory leaks
- Use a ProtectedRoute component to declaratively guard authenticated-only pages
- For one-time auth checks in utility functions, wrap onAuthStateChanged in a Promise that resolves on first callback

## Frequently asked questions

### Why is auth.currentUser null even though the user is signed in?

Firebase Auth is asynchronous. When your app loads, it takes a moment to check for an existing session in the browser. During this initialization period, currentUser is null. Use onAuthStateChanged to wait for the auth state to resolve.

### How long does Firebase Auth take to initialize?

Typically 50-200 milliseconds on a fast connection. It reads the persisted session from IndexedDB or localStorage and validates the token. Always show a loading state during this period rather than assuming the user is not logged in.

### Does onAuthStateChanged fire when the page first loads?

Yes. It fires once immediately after Firebase resolves the auth state (with a User object if signed in, or null if not), and then fires again on every subsequent sign-in or sign-out event.

### How do I get the user's ID token for API calls?

Call user.getIdToken() on the User object from onAuthStateChanged. This returns a Promise that resolves with a JWT you can send to your backend. The token auto-refreshes when expired.

### Can I check if a user's email is verified?

Yes. The User object has an emailVerified boolean property. Check user.emailVerified in your onAuthStateChanged callback or useAuth hook to conditionally allow or restrict access.

### What happens to the auth listener when the browser tab is closed?

The listener is destroyed, but the user session persists in the browser's storage (IndexedDB by default). When the user opens the app again, onAuthStateChanged will fire with the previously signed-in user.

### Should I use onAuthStateChanged or onIdTokenChanged?

Use onAuthStateChanged for most cases — it fires on sign-in and sign-out. Use onIdTokenChanged if you also need to react to ID token refreshes (which happen every hour), such as when syncing tokens to a backend.

---

Source: https://www.rapidevelopers.com/firebase-tutorial/how-to-check-if-user-is-logged-in-with-firebase
© RapidDev — https://www.rapidevelopers.com/firebase-tutorial/how-to-check-if-user-is-logged-in-with-firebase
