I’m having a few problems making my custom firebase provider work on my nextjs website. For example, why is user null in AuthGuard, but it’s not null in firebaseAuthContext. How can I make these two work together when the user i get is always null in AuthGuard, but not in the provider (firebase provider which i custom made). Thank you very much for the help
My AuthGuard
<code>import { ReactNode, useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { useFirebaseAuth } from '@/contexts/FirebaseAuthContext';
import { can } from '@/lib/acl';
import { Loader } from '@/components/Loader';
export type ChildrenType = {
children: ReactNode;
requiredPermission?: { action: string; subject: string };
};
export default function AuthGuard({
children,
requiredPermission
}: ChildrenType) {
const { user, role, isLoggingIn, isLoggingOut } = useFirebaseAuth();
const router = useRouter();
const [isAuthorized, setIsAuthorized] = useState(false);
useEffect(() => {
if (!isLoggingIn) {
if (user && role && requiredPermission) {
if (!can(role, requiredPermission.action, requiredPermission.subject)) {
router.push('/dashboard');
setIsAuthorized(false);
} else {
setIsAuthorized(true);
}
} else {
// Redirect to home if he's not logged in (no user)
console.log('User in AuthGuard', user);
router.push('/home');
}
}
}, [role, isLoggingIn, requiredPermission, router, user]);
if (isLoggingIn || isLoggingOut || !isAuthorized) {
return <Loader />;
}
return <>{children}</>;
}
</code>
<code>import { ReactNode, useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { useFirebaseAuth } from '@/contexts/FirebaseAuthContext';
import { can } from '@/lib/acl';
import { Loader } from '@/components/Loader';
export type ChildrenType = {
children: ReactNode;
requiredPermission?: { action: string; subject: string };
};
export default function AuthGuard({
children,
requiredPermission
}: ChildrenType) {
const { user, role, isLoggingIn, isLoggingOut } = useFirebaseAuth();
const router = useRouter();
const [isAuthorized, setIsAuthorized] = useState(false);
useEffect(() => {
if (!isLoggingIn) {
if (user && role && requiredPermission) {
if (!can(role, requiredPermission.action, requiredPermission.subject)) {
router.push('/dashboard');
setIsAuthorized(false);
} else {
setIsAuthorized(true);
}
} else {
// Redirect to home if he's not logged in (no user)
console.log('User in AuthGuard', user);
router.push('/home');
}
}
}, [role, isLoggingIn, requiredPermission, router, user]);
if (isLoggingIn || isLoggingOut || !isAuthorized) {
return <Loader />;
}
return <>{children}</>;
}
</code>
import { ReactNode, useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { useFirebaseAuth } from '@/contexts/FirebaseAuthContext';
import { can } from '@/lib/acl';
import { Loader } from '@/components/Loader';
export type ChildrenType = {
children: ReactNode;
requiredPermission?: { action: string; subject: string };
};
export default function AuthGuard({
children,
requiredPermission
}: ChildrenType) {
const { user, role, isLoggingIn, isLoggingOut } = useFirebaseAuth();
const router = useRouter();
const [isAuthorized, setIsAuthorized] = useState(false);
useEffect(() => {
if (!isLoggingIn) {
if (user && role && requiredPermission) {
if (!can(role, requiredPermission.action, requiredPermission.subject)) {
router.push('/dashboard');
setIsAuthorized(false);
} else {
setIsAuthorized(true);
}
} else {
// Redirect to home if he's not logged in (no user)
console.log('User in AuthGuard', user);
router.push('/home');
}
}
}, [role, isLoggingIn, requiredPermission, router, user]);
if (isLoggingIn || isLoggingOut || !isAuthorized) {
return <Loader />;
}
return <>{children}</>;
}
My FirebaseAuthProvider
<code> import React, { createContext, useContext, useEffect, useState } from 'react';
import { getAuth, onAuthStateChanged, sendPasswordResetEmail, signOut, User } from 'firebase/auth';
import { app } from '@/config/firebase/utils';
import { Role } from '@/store/types';
import { useRouter } from 'next/router';
import { clearUserSubscriptionData } from '@/store/slices/userSubscriptionSlice';
import { useDispatch } from 'react-redux';
import { AppDispatch } from '@/store';
import { useUserSubscription } from '@/hooks/useUserSubscription';
interface AuthContextType {
user: User | null;
role: Role;
isLoggingIn: boolean;
isLoggingOut: boolean;
logout: () => Promise<void>;
sendForgotPasswordEmail: (email: string) => Promise<void>;
}
const FirebaseAuthContext = createContext<AuthContextType | null>(null);
export const FirebaseAuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [role, setRole] = useState<Role>(Role.VISITOR);
const dispatch = useDispatch<AppDispatch>();
const router = useRouter();
const [isLoggingIn, setIsLoggingIn] = useState(false);
const [isLoggingOut, setIsLoggingOut] = useState(false);
const auth = getAuth(app);
const { fetchUserSubscription } = useUserSubscription();
useEffect(() => {
const authStateChange = onAuthStateChanged(auth, async (user) => {
setIsLoggingIn(true);
if (user) {
setUser(user);
const token = await user.getIdTokenResult(true);
await fetchUserSubscription();
const role = token.claims.role
? (token.claims.role as Role)
: Role.GUEST;
setRole(role);
} else {
setUser(null);
setRole(Role.VISITOR);
dispatch(clearUserSubscriptionData());
}
setIsLoggingIn(false);
});
return () => authStateChange();
}, [auth, user, dispatch]);
const sendForgotPasswordEmail = async (email: string) => {
try {
await sendPasswordResetEmail(auth, email);
} catch (error) {
throw new Error('Unable to send password reset email. Please try again.');
}
};
const logout = async () => {
setIsLoggingOut(true);
try {
await signOut(auth);
setUser(null);
setRole(Role.VISITOR);
dispatch(clearUserSubscriptionData());
router.push('/home');
} catch (error) {
console.error('Logout Error:', error);
} finally {
setIsLoggingOut(false);
}
};
return (
<FirebaseAuthContext.Provider
value={{
user,
role,
isLoggingIn,
isLoggingOut,
logout,
sendForgotPasswordEmail
}}
>
{children}
</FirebaseAuthContext.Provider>
);
};
export const useFirebaseAuth = () => {
const context = useContext(FirebaseAuthContext);
if (!context) {
throw new Error(
'useFirebaseAuth must be used within a FirebaseAuthProvider'
);
}
return context;
};
</code>
<code> import React, { createContext, useContext, useEffect, useState } from 'react';
import { getAuth, onAuthStateChanged, sendPasswordResetEmail, signOut, User } from 'firebase/auth';
import { app } from '@/config/firebase/utils';
import { Role } from '@/store/types';
import { useRouter } from 'next/router';
import { clearUserSubscriptionData } from '@/store/slices/userSubscriptionSlice';
import { useDispatch } from 'react-redux';
import { AppDispatch } from '@/store';
import { useUserSubscription } from '@/hooks/useUserSubscription';
interface AuthContextType {
user: User | null;
role: Role;
isLoggingIn: boolean;
isLoggingOut: boolean;
logout: () => Promise<void>;
sendForgotPasswordEmail: (email: string) => Promise<void>;
}
const FirebaseAuthContext = createContext<AuthContextType | null>(null);
export const FirebaseAuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [role, setRole] = useState<Role>(Role.VISITOR);
const dispatch = useDispatch<AppDispatch>();
const router = useRouter();
const [isLoggingIn, setIsLoggingIn] = useState(false);
const [isLoggingOut, setIsLoggingOut] = useState(false);
const auth = getAuth(app);
const { fetchUserSubscription } = useUserSubscription();
useEffect(() => {
const authStateChange = onAuthStateChanged(auth, async (user) => {
setIsLoggingIn(true);
if (user) {
setUser(user);
const token = await user.getIdTokenResult(true);
await fetchUserSubscription();
const role = token.claims.role
? (token.claims.role as Role)
: Role.GUEST;
setRole(role);
} else {
setUser(null);
setRole(Role.VISITOR);
dispatch(clearUserSubscriptionData());
}
setIsLoggingIn(false);
});
return () => authStateChange();
}, [auth, user, dispatch]);
const sendForgotPasswordEmail = async (email: string) => {
try {
await sendPasswordResetEmail(auth, email);
} catch (error) {
throw new Error('Unable to send password reset email. Please try again.');
}
};
const logout = async () => {
setIsLoggingOut(true);
try {
await signOut(auth);
setUser(null);
setRole(Role.VISITOR);
dispatch(clearUserSubscriptionData());
router.push('/home');
} catch (error) {
console.error('Logout Error:', error);
} finally {
setIsLoggingOut(false);
}
};
return (
<FirebaseAuthContext.Provider
value={{
user,
role,
isLoggingIn,
isLoggingOut,
logout,
sendForgotPasswordEmail
}}
>
{children}
</FirebaseAuthContext.Provider>
);
};
export const useFirebaseAuth = () => {
const context = useContext(FirebaseAuthContext);
if (!context) {
throw new Error(
'useFirebaseAuth must be used within a FirebaseAuthProvider'
);
}
return context;
};
</code>
import React, { createContext, useContext, useEffect, useState } from 'react';
import { getAuth, onAuthStateChanged, sendPasswordResetEmail, signOut, User } from 'firebase/auth';
import { app } from '@/config/firebase/utils';
import { Role } from '@/store/types';
import { useRouter } from 'next/router';
import { clearUserSubscriptionData } from '@/store/slices/userSubscriptionSlice';
import { useDispatch } from 'react-redux';
import { AppDispatch } from '@/store';
import { useUserSubscription } from '@/hooks/useUserSubscription';
interface AuthContextType {
user: User | null;
role: Role;
isLoggingIn: boolean;
isLoggingOut: boolean;
logout: () => Promise<void>;
sendForgotPasswordEmail: (email: string) => Promise<void>;
}
const FirebaseAuthContext = createContext<AuthContextType | null>(null);
export const FirebaseAuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [role, setRole] = useState<Role>(Role.VISITOR);
const dispatch = useDispatch<AppDispatch>();
const router = useRouter();
const [isLoggingIn, setIsLoggingIn] = useState(false);
const [isLoggingOut, setIsLoggingOut] = useState(false);
const auth = getAuth(app);
const { fetchUserSubscription } = useUserSubscription();
useEffect(() => {
const authStateChange = onAuthStateChanged(auth, async (user) => {
setIsLoggingIn(true);
if (user) {
setUser(user);
const token = await user.getIdTokenResult(true);
await fetchUserSubscription();
const role = token.claims.role
? (token.claims.role as Role)
: Role.GUEST;
setRole(role);
} else {
setUser(null);
setRole(Role.VISITOR);
dispatch(clearUserSubscriptionData());
}
setIsLoggingIn(false);
});
return () => authStateChange();
}, [auth, user, dispatch]);
const sendForgotPasswordEmail = async (email: string) => {
try {
await sendPasswordResetEmail(auth, email);
} catch (error) {
throw new Error('Unable to send password reset email. Please try again.');
}
};
const logout = async () => {
setIsLoggingOut(true);
try {
await signOut(auth);
setUser(null);
setRole(Role.VISITOR);
dispatch(clearUserSubscriptionData());
router.push('/home');
} catch (error) {
console.error('Logout Error:', error);
} finally {
setIsLoggingOut(false);
}
};
return (
<FirebaseAuthContext.Provider
value={{
user,
role,
isLoggingIn,
isLoggingOut,
logout,
sendForgotPasswordEmail
}}
>
{children}
</FirebaseAuthContext.Provider>
);
};
export const useFirebaseAuth = () => {
const context = useContext(FirebaseAuthContext);
if (!context) {
throw new Error(
'useFirebaseAuth must be used within a FirebaseAuthProvider'
);
}
return context;
};