From f5432151b13fcb5ef1f31bf9acecd59e90bcce81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Even=20=C3=98sterud?= Date: Mon, 6 Oct 2025 15:18:15 +0200 Subject: [PATCH] Fix mobile authentication - add popup-first strategy with redirect fallback - Replace device-specific auth methods with popup-first approach for all devices - Add automatic fallback to redirect when popup fails (auth/popup-blocked) - Implement comprehensive auth state listener and redirect result handling - Add explicit browserLocalPersistence for better mobile compatibility - Include proper navigation after successful authentication - Improve error handling with specific popup failure detection Fixes mobile Safari 'disallowed_useragent' error and authentication persistence issues --- frontend/app/components/LoginForm.tsx | 80 +++++++++++++++++++++++++-- frontend/lib/firebase.ts | 8 ++- 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/frontend/app/components/LoginForm.tsx b/frontend/app/components/LoginForm.tsx index 8091916..cd41ae1 100644 --- a/frontend/app/components/LoginForm.tsx +++ b/frontend/app/components/LoginForm.tsx @@ -2,11 +2,12 @@ "use client"; // --- Imports --- -import { useState } from "react"; +import { useState, useEffect } from "react"; import Link from "next/link"; import Image from "next/image"; +import { useRouter } from "next/navigation"; import { auth } from "@/lib/firebase"; -import { signInWithPopup, GoogleAuthProvider } from "firebase/auth"; +import { signInWithRedirect, signInWithPopup, getRedirectResult, GoogleAuthProvider, onAuthStateChanged } from "firebase/auth"; import { FirebaseError } from "firebase/app"; import Modal from "./Modal"; import InfoModal from "./InfoModal"; @@ -17,25 +18,92 @@ export default function LoginForm() { const [error, setError] = useState(""); const [isLoading, setIsLoading] = useState(false); const [showInfoModal, setShowInfoModal] = useState(false); + const router = useRouter(); + + // Detect if we're on mobile or PWA + const isMobileOrPWA = () => { + const userAgent = navigator.userAgent.toLowerCase(); + const isMobile = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent); + const isPWA = window.matchMedia('(display-mode: standalone)').matches; + return isMobile || isPWA; + }; // --- Google OAuth Login Handler --- const loginGoogle = async () => { - if (!auth) return; + if (!auth) { + console.error("Firebase not initialized!"); + return; + } setIsLoading(true); setError(""); try { const provider = new GoogleAuthProvider(); - await signInWithPopup(auth, provider); + provider.setCustomParameters({ + prompt: 'select_account' + }); + + // Try popup first for all devices, fall back to redirect only if popup fails + try { + const result = await signInWithPopup(auth, provider); + console.log("Popup login successful:", result.user.email); + setIsLoading(true); + setTimeout(() => { + router.push('/'); + }, 500); + return; + } catch (popupError) { + const fbErr = popupError as FirebaseError; + console.log("Popup failed, trying redirect:", fbErr.code); + + // Only use redirect if popup specifically fails + if (fbErr.code === 'auth/popup-blocked' || fbErr.code === 'auth/popup-closed-by-user') { + await signInWithRedirect(auth, provider); + } else { + throw popupError; // Re-throw if it's a different error + } + } } catch (err) { const fbErr = err as FirebaseError; + console.error("Login error:", fbErr.code, fbErr.message); setError("Feil ved innlogging. Prøv igjen."); - console.error("Login error:", fbErr.message); - } finally { setIsLoading(false); } }; + // --- Handle redirect result and auth state changes --- + useEffect(() => { + if (!auth) { + console.log("No auth instance available"); + return; + } + + // Simple auth state listener + const unsubscribe = onAuthStateChanged(auth, (user) => { + if (user) { + console.log("User is authenticated:", user.email); + setIsLoading(true); + setTimeout(() => { + router.push('/'); + }, 500); + } else { + console.log("No authenticated user"); + setIsLoading(false); + } + }); + + // Check for any pending redirect result + getRedirectResult(auth).then((result) => { + if (result) { + console.log("Redirect result found:", result.user.email); + } + }).catch((error) => { + console.error("Error checking redirect result:", error); + }); + + return () => unsubscribe(); + }, [router]); + // --- Render Login Form --- return (
diff --git a/frontend/lib/firebase.ts b/frontend/lib/firebase.ts index 97028c7..882ab95 100644 --- a/frontend/lib/firebase.ts +++ b/frontend/lib/firebase.ts @@ -1,6 +1,6 @@ // frontend/lib/firebase.ts import { initializeApp, getApps, getApp, FirebaseApp } from "firebase/app"; -import { getAuth, Auth } from "firebase/auth"; +import { getAuth, Auth, setPersistence, browserLocalPersistence } from "firebase/auth"; import { getFirestore, Firestore } from "firebase/firestore"; // Check if we're in a build environment without proper env vars @@ -30,6 +30,12 @@ let db: Firestore | null = null; if (typeof window !== 'undefined' && isValidConfig) { app = !getApps().length ? initializeApp(firebaseConfig) : getApp(); auth = getAuth(app); + + // Set persistence to local storage for mobile compatibility + setPersistence(auth, browserLocalPersistence).catch((error) => { + console.error("Failed to set auth persistence:", error); + }); + db = getFirestore(app); }