I’m trying to authenticate a user by using Firebase SignInWithRedirect while hosting my app on Firebase. When the user successfully logs into Google and the app returns from the redirect, the getRedirectResult function detects the redirect but returns null, causing the app to think the user was never logged in.
I’ve thoroughly checked the Firebase documentation, authorized domains, and the Firebase configuration in my firebase.js authentication file. I verified that my setup works perfectly with SignInWithPopup, but for mobile compatibility, I prefer to use SignInWithRedirect. The problem seems to persist only with the redirect flow.
What I’ve tried so far:
-
Verified that the domain is correctly added in Firebase Auth.
-
Checked the Firebase configuration file (firebase.js), and it’s set up as per Firebase’s documentation.
-
Ensured that my Firebase-hosted app is correctly linked to my Firebase configuration in the project settings.
-
Tried clearing browser history, cache, and even added the domain manually to the browser’s third-party cookies settings.
-
I also have a .env file in my project, containing sensitive information like API keys, which I don’t want to expose to the public. The .env file is correctly used for environment variables, and it’s not included in my public code.
Despite all of this, after successfully returning from Google authentication, the result from getRedirectResult remains null.
File Structure:
Has anyone encountered this issue or found a solution for the recent Firebase changes? Any insights would be appreciated!
firebase.js
// Import the functions you need from the SDKs
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { getDatabase } from "firebase/database";
// Your web app's Firebase configuration using environment variables
const firebaseConfig = {
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_FIREBASE_APP_ID,
measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID,
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Optional: Initialize Firebase Analytics if needed
// Uncomment if you decide to use Analytics in the future
// import { getAnalytics } from "firebase/analytics";
// const analytics = getAnalytics(app);
// Initialize Firebase services
export const auth = getAuth(app);
export const db = getFirestore(app);
export const realtimeDb = getDatabase(app);
AuthContext.js
import React, { createContext, useContext, useState, useEffect } from "react";
import {
onAuthStateChanged,
GoogleAuthProvider,
signOut,
signInWithRedirect,
getRedirectResult,
} from "firebase/auth";
import { auth } from "./firebase";
// Create the AuthContext
const AuthContext = createContext();
// Custom hook to use the AuthContext
export const useAuth = () => useContext(AuthContext);
// AuthProvider component that wraps the app and manages auth state
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null); // Holds the authenticated user
const [googleData, setGoogleData] = useState(null); // Holds the user info from Google
const [signupComplete] = useState(false); // Track if the signup process is complete
const [loading, setLoading] = useState(true); // Manage loading state while checking auth
const googleProvider = new GoogleAuthProvider();
googleProvider.setCustomParameters({
prompt: "select_account", // Always prompt user to select an account
});
const loginWithGoogle = async () => {
signInWithRedirect(auth, googleProvider);
};
// Use getRedirectResult to handle the result of a sign-in with redirect
useEffect(() => {
const handleRedirectResult = async () => {
try {
const result = await getRedirectResult(auth);
console.log("Result is:", result)
if (result) {
const user = result.user;
setCurrentUser(user);
setGoogleData({
fullName: user.displayName,
email: user.email,
uid: user.uid,
photoURL: user.photoURL,
});
console.log("Redirect sign-in successful, user:", user);
}
} catch (error) {
console.error("Error handling redirect result:", error);
}
};
handleRedirectResult(); // Call function to handle redirect result
}, []);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
setCurrentUser(user);
setGoogleData({
fullName: user.displayName,
email: user.email,
uid: user.uid, // Get user UID from Google
photoURL: user.photoURL,
});
console.log("User authenticated:", user);
} else {
setCurrentUser(null);
setGoogleData(null); // Clear user data on logout
}
setLoading(false); // Set loading to false once auth state is determined
});
return () => unsubscribe(); // Clean up the subscription
}, []);
// Logout logic, clears currentUser and googleData
const logout = async () => {
console.log("Signing out");
try {
await signOut(auth);
setCurrentUser(null); // Clear currentUser on logout
setGoogleData(null); // Clear googleData on logout
} catch (error) {
console.error("Logout Error: ", error);
}
};
// The value provided to components via AuthContext
const value = {
currentUser,
googleData, // Provide googleData for use in components
signupComplete, // Track signup completion status
loginWithGoogle,
logout,
loading, // Pass loading state to prevent premature renders
};
// Render the children inside the context provider
return (
<AuthContext.Provider value={value}>
{!loading && children} {/* Ensure that content is only rendered after auth check */}
</AuthContext.Provider>
);
};
LoginForm/index.jsx
import React, { createContext, useContext, useState, useEffect } from "react";
import {
onAuthStateChanged,
GoogleAuthProvider,
signOut,
signInWithRedirect,
getRedirectResult,
} from "firebase/auth";
import { auth } from "./firebase";
// Create the AuthContext
const AuthContext = createContext();
// Custom hook to use the AuthContext
export const useAuth = () => useContext(AuthContext);
// AuthProvider component that wraps the app and manages auth state
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null); // Holds the authenticated user
const [googleData, setGoogleData] = useState(null); // Holds the user info from Google
const [signupComplete] = useState(false); // Track if the signup process is complete
const [loading, setLoading] = useState(true); // Manage loading state while checking auth
const googleProvider = new GoogleAuthProvider();
googleProvider.setCustomParameters({
prompt: "select_account", // Always prompt user to select an account
});
const loginWithGoogle = async () => {
signInWithRedirect(auth, googleProvider);
};
// Use getRedirectResult to handle the result of a sign-in with redirect
useEffect(() => {
const handleRedirectResult = async () => {
try {
const result = await getRedirectResult(auth);
console.log("Result is:", result)
if (result) {
const user = result.user;
setCurrentUser(user);
setGoogleData({
fullName: user.displayName,
email: user.email,
uid: user.uid,
photoURL: user.photoURL,
});
console.log("Redirect sign-in successful, user:", user);
}
} catch (error) {
console.error("Error handling redirect result:", error);
}
};
handleRedirectResult(); // Call function to handle redirect result
}, []);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
setCurrentUser(user);
setGoogleData({
fullName: user.displayName,
email: user.email,
uid: user.uid, // Get user UID from Google
photoURL: user.photoURL,
});
console.log("User authenticated:", user);
} else {
setCurrentUser(null);
setGoogleData(null); // Clear user data on logout
}
setLoading(false); // Set loading to false once auth state is determined
});
return () => unsubscribe(); // Clean up the subscription
}, []);
// Logout logic, clears currentUser and googleData
const logout = async () => {
console.log("Signing out");
try {
await signOut(auth);
setCurrentUser(null); // Clear currentUser on logout
setGoogleData(null); // Clear googleData on logout
} catch (error) {
console.error("Logout Error: ", error);
}
};
// The value provided to components via AuthContext
const value = {
currentUser,
googleData, // Provide googleData for use in components
signupComplete, // Track signup completion status
loginWithGoogle,
logout,
loading, // Pass loading state to prevent premature renders
};
// Render the children inside the context provider
return (
<AuthContext.Provider value={value}>
{!loading && children} {/* Ensure that content is only rendered after auth check */}
</AuthContext.Provider>
);
};
The flow is as follows:
-
A user clicks the “Sign In” button, which triggers the loginWithGoogle function inside the AuthContext.js file. This function calls signInWithRedirect to initiate the Google login process.
-
The user is redirected to the Google login page, where they enter their credentials and successfully log in.
-
After login, the user is redirected back to my application. In the AuthContext.js file, the getRedirectResult function is expected to retrieve the authentication result and provide the logged-in user’s information.
The issue: After the user is redirected back to the app, the getRedirectResult function detects the redirect but returns null instead of the expected user authentication result. This causes the app to assume the user is not logged in, even though the login process was successful on Google’s side.