πŸ” Firebase Authentication

Firebase Authentication Guide

This application uses Firebase Authentication for secure user authentication with Google OAuth, email/password, and session management.

Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   Firebase Auth Flow                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  1. User clicks "Sign in with Google"                       β”‚
β”‚     ↓                                                       β”‚
β”‚  2. Firebase opens Google OAuth popup                       β”‚
β”‚     ↓                                                       β”‚
β”‚  3. User authenticates with Google                          β”‚
β”‚     ↓                                                       β”‚
β”‚  4. Firebase returns UserCredential                         β”‚
β”‚     ↓                                                       β”‚
β”‚  5. App redirects to /access (Smart Routing Proxy)          β”‚
β”‚     ↓                                                       β”‚
β”‚  6. /access determines:                                     β”‚
β”‚     - First-time user (< 5 min old) β†’ /onboarding           β”‚
β”‚     - Returning user β†’ /dashboard/chat                      β”‚
β”‚     - Custom redirect β†’ specified URL                       β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

File Structure

lib/
β”œβ”€β”€ firebase/
β”‚   β”œβ”€β”€ config.ts                    # Firebase config & initialization
β”‚   └── auth.ts                      # Auth utilities
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ firebase-auth.service.ts     # Main auth service (all methods)
β”‚   β”œβ”€β”€ firebase-user.service.ts     # User profile management
β”‚   └── firebase-project.service.ts  # Project management
contexts/
└── AuthContext.tsx                  # React context for auth state
app/
β”œβ”€β”€ (marketing)/auth/
β”‚   β”œβ”€β”€ login/page.tsx              # Login page
β”‚   └── register/page.tsx           # Registration page
β”œβ”€β”€ __/auth/handler/page.tsx        # Firebase redirect handler
└── access/page.tsx                 # Smart routing proxy
components/
└── auth/
    └── simple-auth-form.tsx        # Auth form component

Setup & Configuration

Create Firebase Project

  1. Create a Firebase project at console.firebase.google.com (opens in a new tab)
  2. Enable Authentication β†’ Google Sign-In
  3. Add authorized domains (localhost, your-domain.com)
  4. Get your Firebase config

Configure Environment Variables

Create .env.local:

# Firebase Configuration
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSy...
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-app.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-app.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=123456789
NEXT_PUBLIC_FIREBASE_APP_ID=1:123456789:web:abc123
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=G-ABC123XYZ
 
# Optional: Custom auth domain (for Next.js 16 compatibility)
NEXT_PUBLIC_FIREBASE_CUSTOM_AUTH_DOMAIN=altsportsleagues.ai

Initialize Firebase

// lib/firebase/config.ts
import { initializeApp, getApps } from 'firebase/app'
import { getAuth, GoogleAuthProvider } from 'firebase/auth'
 
const firebaseConfig = {
  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,
  measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
}
 
// Initialize Firebase (singleton pattern)
const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0]
 
export const auth = getAuth(app)
export const googleProvider = new GoogleAuthProvider()
export default app

Authentication Methods

Popup Method (Recommended)

import { FirebaseAuthService } from '@/lib/services/firebase-auth.service'
 
const handleGoogleSignIn = async () => {
  try {
    await FirebaseAuthService.signInWithGoogle()
    router.push('/access') // Smart routing proxy
  } catch (error: any) {
    if (error.message === 'POPUP_CLOSED') {
      toast.info('Sign-in cancelled. Click "Continue with Google" to try again.')
    } else if (error.message === 'Popup blocked - redirecting to Google sign-in') {
      toast.info('Popup blocked. Redirecting to Google...')
    } else {
      toast.error('Failed to sign in with Google')
    }
  }
}

Smart Routing Proxy

The /access route intelligently routes users based on their state:

// app/access/page.tsx
 
const getSmartDestination = async (user: User): Promise<string> => {
  // 1. Check for external redirect URL
  if (redirectUrl) return redirectUrl
 
  // 2. Check for internal return URL
  if (returnTo) return returnTo
 
  // 3. Determine if first-time or returning user
  const creationTime = user.metadata?.creationTime
  if (creationTime) {
    const minutesSinceCreation = getMinutesSince(creationTime)
 
    // First-time user (< 5 minutes old)
    if (minutesSinceCreation < 5) {
      return '/onboarding'
    }
  }
 
  // 4. Default: returning user
  return '/dashboard/chat'
}

Usage Examples

// Basic: Let smart routing decide
router.push('/access')
 
// With return URL (after protected route redirect)
router.push('/access?returnTo=/dashboard/markets')
 
// With external redirect (for cross-domain auth)
router.push('/access?redirectUrl=https://external.com/callback')

API Reference

FirebaseAuthService Methods

// Sign-In Methods
static async signInWithGoogle(): Promise<UserCredential>
static async signInWithEmail(email: string, password: string): Promise<UserCredential>
static async createAccount(email: string, password: string, displayName?: string): Promise<UserCredential>
static async signOut(): Promise<void>
static async handleRedirectResult(): Promise<UserCredential | null>
 
// User Management
static getCurrentUser(): User | null
static toAuthUser(user: User): AuthUser
static isAuthenticated(): boolean
static async getIdToken(forceRefresh = false): Promise<string | null>
static onAuthStateChange(callback: (user: User | null) => void): () => void
 
// Profile Updates
static async resetPassword(email: string): Promise<void>
static async updateUserProfile(updates: { displayName?: string; photoURL?: string }): Promise<void>
static async updateUserEmail(newEmail: string, password: string): Promise<void>
static async updateUserPassword(currentPassword: string, newPassword: string): Promise<void>

AuthContext Hook

'use client'
 
import { useAuth } from '@/contexts/AuthContext'
 
export function UserProfile() {
  const {
    user,              // Firebase User | null
    userProfile,       // Extended profile data
    loading,           // Auth state loading
    signInWithGoogle,  // Sign-in method
    signOut,           // Sign-out method
  } = useAuth()
 
  if (loading) return <LoadingSpinner />
  if (!user) return <LoginPrompt />
 
  return <div>Welcome, {user.displayName}!</div>
}

Protected Routes

Layout-Level Protection

// app/(dashboard)/dashboard/layout.tsx
'use client'
 
import { useAuth } from '@/contexts/AuthContext'
import { redirect } from 'next/navigation'
import { useEffect } from 'react'
 
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  const { user, loading } = useAuth()
 
  useEffect(() => {
    if (!loading && !user) {
      redirect('/auth/login')
    }
  }, [user, loading])
 
  if (loading) return <LoadingScreen />
  if (!user) return null
 
  return <DashboardLayout>{children}</DashboardLayout>
}

Error Handling

Common Firebase Auth Errors

const handleAuthError = (error: any) => {
  switch (error.code) {
    case 'auth/popup-closed-by-user':
      toast.info('Sign-in cancelled. Try again when ready.')
      break
    case 'auth/popup-blocked':
      toast.warning('Popup blocked. Redirecting to Google...')
      break
    case 'auth/user-not-found':
      toast.error('No account found with this email.')
      break
    case 'auth/wrong-password':
      toast.error('Incorrect password.')
      break
    case 'auth/email-already-in-use':
      toast.error('Email already registered. Try signing in.')
      break
    case 'auth/weak-password':
      toast.error('Password too weak. Use at least 8 characters.')
      break
    case 'auth/too-many-requests':
      toast.error('Too many attempts. Try again later.')
      break
    default:
      toast.error(error.message || 'Authentication failed')
  }
}

Best Practices

⚠️

Use Popup Method for OAuth - Popup prevents redirect loops in Next.js 16 and provides better UX.

Recommended Patterns

// βœ… Good: Use popup method
await signInWithPopup(auth, googleProvider)
 
// ❌ Avoid: Causes redirect loops in Next.js 16
await signInWithRedirect(auth, googleProvider)
 
// βœ… Good: Handle popup-blocked gracefully
try {
  await signInWithPopup(auth, googleProvider)
} catch (error: any) {
  if (error.code === 'auth/popup-blocked') {
    await signInWithRedirect(auth, googleProvider)
    toast.info('Popup blocked. Redirecting to Google...')
  }
}
 
// βœ… Good: Always use smart routing
router.push('/access')
 
// ❌ Avoid: Hardcoded destination
router.push('/dashboard/explore')

Security Rules

Firestore Security Rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // User profiles - users can only read/write their own
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
 
    // Leagues - read public, write owner only
    match /leagues/{leagueId} {
      allow read: if true;
      allow write: if request.auth != null &&
                      request.auth.uid == resource.data.ownerUid;
    }
  }
}

Troubleshooting

IssueSolution
"Popup closed by user" errorHandle gracefully - user cancelled
Popup blocked by browserFallback to redirect method
Redirect loop in Next.js 16Use popup method instead
"Firebase auth not initialized"Check environment variables
User state not persistingEnsure AuthContext wraps your app

Related Documentation

Platform

Documentation

Community

Support

partnership@altsportsdata.comdev@altsportsleagues.ai

2025 Β© AltSportsLeagues.ai. Powered by AI-driven sports business intelligence.

πŸ€– AI-Enhancedβ€’πŸ“Š Data-Drivenβ€’βš‘ Real-Time