# [Auth] Implement Firebase Authentication via krowSDK Facade Labels: feature, infra, platform:web, platform:backend, priority:high, sred-eligible Milestone: Foundation & Dev Environment Setup ### 🎯 Objective Replace the Base44 authentication client with a new internal SDK module, `krowSDK.auth`, backed by Firebase Authentication. This foundational task will unblock all future backend development and align the web application with the new GCP-based architecture. ### 🔬 SR&ED Justification - **Technological Uncertainty:** What is the optimal way to create a seamless abstraction layer (`krowSDK`) that perfectly mimics the existing `base44` SDK's interface to minimize frontend refactoring, while integrating a completely different authentication provider (Firebase Auth)? A key uncertainty is how this facade will handle the significant differences in user session management and data retrieval (e.g., custom user fields like `user_role` which are not native to the Firebase Auth user object) between the two systems. - **Systematic Investigation:** We will conduct experimental development to build a `krowSDK.auth` module. This involves systematically mapping each `base44.auth` method (`me`, `logout`, `isAuthenticated`) to its Firebase Auth equivalent. We will investigate and prototype a solution for fetching supplementary user data (like `user_role`) from our Firestore database and merging it with the core Firebase Auth user object. This will establish a clean, reusable, and scalable architectural pattern for all future SDK modules. ### Details This task is the most critical prerequisite for migrating our backend. As defined in the `03-backend-api-specification.md`, every request to our new Data Connect and Cloud Functions API will require a `Bearer `. Without this, no backend work can proceed. #### The Strategy: A Facade SDK (`krowSDK`) Instead of replacing `base44` calls with Firebase calls directly throughout the codebase, we will create an abstraction layer, or "Facade". This approach has three major benefits: 1. **Simplified Migration:** The new `krowSDK.auth` will expose the *exact same methods* as the old `base44.auth`. This means we can swap out the authentication logic with minimal changes to the UI components, drastically reducing the scope of refactoring. 2. **High Maintainability:** All authentication logic will be centralized in one place. If we ever need to change providers again, we only modify the SDK, not the entire application. 3. **Clear Separation of Concerns:** The UI components remain agnostic about the authentication provider. They just need to call `krowSDK.auth.me()`, not worry about the underlying implementation details. #### Implementation Plan The developer should create two new files: **1. Firebase Configuration (`frontend-web/src/firebase/config.js`)** This file will initialize the Firebase app and export the necessary services. It's crucial to use environment variables for the configuration keys to keep them secure and environment-specific. ```javascript // frontend-web/src/firebase/config.js import { initializeApp } from "firebase/app"; import { getAuth } from "firebase/auth"; import { getFirestore } from "firebase/firestore"; // Your web app's Firebase configuration // IMPORTANT: Use environment variables for these values const firebaseConfig = { apiKey: import.meta.env.VITE_FIREBASE_API_KEY, authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN, projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID, storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET, messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID, appId: import.meta.env.VITE_FIREBASE_APP_ID }; // Initialize Firebase const app = initializeApp(firebaseConfig); // Export Firebase services export const auth = getAuth(app); export const db = getFirestore(app); export default app; ``` **2. Krow SDK (`frontend-web/src/lib/krowSDK.js`)** This is the core of the task. This file will implement the facade, importing the Firebase `auth` service and recreating the `base44.auth` interface. ```javascript // frontend-web/src/lib/krowSDK.js import { auth, db } from '../firebase/config'; import { onAuthStateChanged, signOut, updateProfile, // Import other necessary auth functions like signInWithEmailAndPassword, createUserWithEmailAndPassword, etc. } from "firebase/auth"; import { doc, getDoc } from "firebase/firestore"; /** * A promise-based wrapper for onAuthStateChanged to check the current auth state. * @returns {Promise} - A promise that resolves to true if authenticated, false otherwise. */ const isAuthenticated = () => { return new Promise((resolve) => { const unsubscribe = onAuthStateChanged(auth, (user) => { unsubscribe(); resolve(!!user); }); }); }; /** * Fetches the current authenticated user's profile. * This function mimics `base44.auth.me()` by combining the Firebase Auth user * with custom data from our Firestore database (e.g., user_role). * @returns {Promise} - A promise that resolves with the user object or null. */ const me = () => { return new Promise((resolve, reject) => { const unsubscribe = onAuthStateChanged(auth, async (user) => { unsubscribe(); if (user) { try { // 1. Get the core user from Firebase Auth const { uid, email, displayName } = user; const baseProfile = { id: uid, email: email, full_name: displayName, }; // 2. Get custom fields from Firestore // We assume a 'users' collection where the document ID is the user's UID. const userDocRef = doc(db, "users", uid); const userDoc = await getDoc(userDocRef); if (userDoc.exists()) { const customData = userDoc.data(); // 3. Merge the two data sources resolve({ ...baseProfile, user_role: customData.user_role, // Example custom field // ... other custom fields from Firestore }); } else { // User exists in Auth but not in Firestore. This can happen during sign-up. // Resolve with base profile; the app should handle creating the Firestore doc. resolve(baseProfile); } } catch (error) { console.error("Error fetching user profile from Firestore:", error); reject(error); } } else { // No user is signed in. resolve(null); } }); }); }; /** * Updates the current user's profile. * This mimics `base44.auth.updateMe()`. * @param {object} profileData - Data to update, e.g., { full_name: "New Name" }. */ const updateMe = async (profileData) => { if (auth.currentUser) { // Firebase Auth's updateProfile only supports displayName and photoURL. if (profileData.full_name) { await updateProfile(auth.currentUser, { displayName: profileData.full_name, }); } // For other custom fields, you would need to update the user's document in Firestore. // const userDocRef = doc(db, "users", auth.currentUser.uid); // await updateDoc(userDocRef, { custom_field: profileData.custom_field }); } else { throw new Error("No authenticated user to update."); } }; /** * Logs the user out and redirects them. * @param {string} [redirectUrl='/'] - The URL to redirect to after logout. */ const logout = (redirectUrl = '/') => { signOut(auth).then(() => { // Redirect after sign-out. window.location.href = redirectUrl; }).catch((error) => { console.error("Logout failed:", error); }); }; // The krowSDK object that mimics the Base44 SDK structure export const krowSDK = { auth: { isAuthenticated, me, updateMe, logout, // Note: redirectToLogin is not implemented as it's a concern of the routing library (e.g., React Router), // which should protect routes and redirect based on the authentication state. }, // Future modules will be added here, e.g., krowSDK.entities.Event }; ``` ### ✅ Acceptance Criteria - [ ] A `frontend-web/src/firebase/config.js` file is created, correctly initializing the Firebase app using environment variables. - [ ] A `frontend-web/src/lib/krowSDK.js` file is created and implements the `krowSDK.auth` facade. - [ ] The `krowSDK.auth` module exports `isAuthenticated`, `me`, `updateMe`, and `logout` functions with interfaces identical to their `base44.auth` counterparts. - [ ] Key parts of the application (e.g., login pages, user profile components, auth checks) are refactored to import and use `krowSDK.auth` instead of `base44.auth`. - [ ] A new user can sign up using a Firebase-powered form. - [ ] An existing user can log in using a Firebase-powered form. - [ ] The application UI correctly updates to reflect the user's authenticated state (e.g., showing user name, hiding login button). - [ ] After logging in, the user's Firebase Auth ID Token can be retrieved and is ready to be sent in an `Authorization: Bearer` header for API calls.