# NextJS & Firebase Integration

### Introduction to Next.js with Firebase Authentication

This project demonstrates how to integrate **Firebase Authentication** with a **Next.js** application, providing a secure and efficient way to manage user authentication. Firebase offers a robust backend-as-a-service platform, and its authentication module seamlessly handles user sign-ins, sign-outs, and session management.

**Key Features:**
- **Next.js Middleware**: Protect routes by checking user authentication status using cookies.
- **Firebase Authentication**: Use Firebase to manage user accounts, handle sign-in and sign-out, and monitor authentication state changes.
- **Global State Management**: Implement a React Context (`AuthContext`) to manage and share authentication state across the application.
- **Environment Variables**: Keep sensitive Firebase credentials secure using `.env.local`.

By following this guide, you'll set up a fully functional authentication system with protected routes and dynamic user session handling, all within the simplicity and performance of Next.js. Whether you're building a small project or a large-scale application, this setup is a solid foundation for adding authentication to your app.

---

## **Project Structure**

Here's the suggested structure for a project that includes Firebase authentication in Next.js:

```
project-root/
├── components/
│   ├── AuthProvider.jsx
├── context/
│   ├── AuthContext.jsx
├── lib/
│   ├── firebase.js
├── middleware.ts
├── pages/
│   ├── _app.js
│   ├── index.js
│   ├── login.js
│   ├── dashboard.js
├── styles/
│   ├── globals.css
├── .env.local
├── next.config.js
├── package.json
```

---

## **Detailed Explanation**

### **`lib/firebase.js`**
Your `firebase.js` file initializes Firebase. The code you've provided is correct and initializes the Firebase app only once. It also sets up the `auth` and `db` exports for authentication and Firestore usage.

Here's the `lib/firebase.js` file that initializes Firebase for your Next.js project:

```javascript
// lib/firebase.js
import { initializeApp, getApps } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";

// Firebase configuration using environment variables
const clientCredentials = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};

// Initialize Firebase only if no apps are already initialized
let firebaseApp;
if (!getApps().length) {
  firebaseApp = initializeApp(clientCredentials);
} else {
  firebaseApp = getApps()[0];
}

// Export Firebase Auth and Firestore instances
export const auth = getAuth(firebaseApp);
export const db = getFirestore(firebaseApp);
```

### Explanation:
1. **Environment Variables**:
   - The configuration values (API key, Auth domain, etc.) are stored in `.env.local` and accessed using `process.env`.
   - This ensures sensitive information is not exposed in your codebase.

2. **Firebase Initialization**:
   - The `initializeApp` function is called only if no Firebase apps are already initialized. This avoids reinitialization errors, which can occur during development due to hot reloading.

3. **Exported Modules**:
   - `auth`: Provides Firebase Authentication features.
   - `db`: Connects to Firestore, Firebase's NoSQL database.

This file will serve as the core configuration for Firebase in your application. You can import `auth` and `db` from this file whenever you need to use Firebase Authentication or Firestore in your project.

### **`context/AuthContext.jsx`**
This file will manage the authentication state using React Context.

```javascript
import { createContext, useContext, useState, useEffect } from "react";
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "../lib/firebase";

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      setUser(user);
      setLoading(false);
    });
    return unsubscribe;
  }, []);

  return (
    <AuthContext.Provider value={{ user, loading }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  return useContext(AuthContext);
}
```

---

### **`middleware.ts`**
Your middleware ensures users cannot access protected pages without logging in.

The code you've provided works perfectly. Just ensure cookies like `isLoggedIn` are set properly during login.

Here's the `middleware.ts` file for your Next.js project:

```typescript
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  // Check if the user is authenticated by looking for the 'isLoggedIn' cookie
  const isLoggedIn = request.cookies.get("isLoggedIn");

  console.log("Authentication status (isLoggedIn):", isLoggedIn);

  // Redirect to /login if the user is not logged in and tries to access protected routes
  if (!isLoggedIn && request.nextUrl.pathname !== "/login") {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  // Proceed to the requested route if the user is authenticated or on the login page
  return NextResponse.next();
}

// Apply middleware to all routes except /login
export const config = {
  matcher: ["/((?!login).*)"], // Matches all routes except /login
};
```

---

### Explanation:

1. **`isLoggedIn` Cookie**:
   - The middleware checks for a cookie named `isLoggedIn`, which should be set after a successful login.

2. **Redirection**:
   - If the cookie is not present and the user is attempting to access a route other than `/login`, they are redirected to the `/login` page.

3. **`NextResponse.next()`**:
   - Allows the request to proceed if the user is authenticated or accessing `/login`.

4. **`matcher` Configuration**:
   - Ensures the middleware runs for all routes except `/login`.

---

### **`pages/_app.js`**
Wrap your application with the `AuthProvider`.

```javascript
import { AuthProvider } from "../context/AuthContext";
import "../styles/globals.css";

function MyApp({ Component, pageProps }) {
  return (
    <AuthProvider>
      <Component {...pageProps} />
    </AuthProvider>
  );
}

export default MyApp;
```

---

### **`pages/login.js`**
Create a login page using Firebase Authentication.

```javascript
import { useState } from "react";
import { signInWithEmailAndPassword } from "firebase/auth";
import { useRouter } from "next/router";
import { auth } from "../lib/firebase";

export default function Login() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const router = useRouter();

  const handleLogin = async (e) => {
    e.preventDefault();
    try {
      await signInWithEmailAndPassword(auth, email, password);
      document.cookie = "isLoggedIn=true"; // Set the cookie
      router.push("/dashboard");
    } catch (err) {
      setError("Invalid credentials");
    }
  };

  return (
    <div>
      <h1>Login</h1>
      {error && <p>{error}</p>}
      <form onSubmit={handleLogin}>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="Email"
          required
        />
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          placeholder="Password"
          required
        />
        <button type="submit">Login</button>
      </form>
    </div>
  );
}
```

---

### **`pages/dashboard.js`**
A protected page that requires authentication.

```javascript
import { useAuth } from "../context/AuthContext";
import { useRouter } from "next/router";

export default function Dashboard() {
  const { user, loading } = useAuth();
  const router = useRouter();

  if (loading) return <p>Loading...</p>;

  if (!user) {
    router.push("/login");
    return null;
  }

  return (
    <div>
      <h1>Welcome, {user.email}</h1>
      <button
        onClick={() => {
          auth.signOut();
          document.cookie = "isLoggedIn=; Max-Age=0"; // Clear the cookie
          router.push("/login");
        }}
      >
        Logout
      </button>
    </div>
  );
}
```

---

### **`.env.local`**
Environment variables for Firebase configuration.

```
NEXT_PUBLIC_FIREBASE_API_KEY=your-api-key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-auth-domain
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-storage-bucket
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your-messaging-sender-id
NEXT_PUBLIC_FIREBASE_APP_ID=your-app-id
```

---

### **Key Features of This Setup**

1. **Firebase Authentication**: Using `AuthContext` to manage authentication globally.
2. **Middleware**: Protects routes using `isLoggedIn` cookie.
3. **Environment Variables**: Ensures sensitive keys are not hard-coded.
4. **Protected Routes**: The dashboard only allows access if the user is authenticated.

This structure provides a secure and scalable authentication system for your Next.js project using Firebase.
