直接链接导航 firebase 上的用户为空

问题描述 投票:0回答:1

当用户没有访问页面所需的权限时,如何重定向到我的主页。我正在使用这个定制的 AuthGuard

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, loading } = useFirebaseAuth();
  const router = useRouter();
  const [isAuthorized, setIsAuthorized] = useState(false);

  useEffect(() => {
    console.log('Loading', loading);
    console.log('AuthGuard user', user);
    if (!loading) {
      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)
      //   router.push('/home');
      // }
    }
  }, [role, loading, requiredPermission, router, user]);

  if (loading || !isAuthorized) {
    return <Loader />;
  }

  return <>{children}</>;
}

不幸的是,使用这个 AuthGuard,如果我注释掉 router.push 到主页,当我直接导航到经过身份验证和未经身份验证的用户的链接时,我总是会被重定向到主页,所以我将其注释掉,现在它对于经过身份验证的用户可以正常工作。我可以直接导航到受保护的链接,并且不会重定向到经过身份验证的用户的主页,但现在的问题是我仍然想重定向到未经身份验证的用户的主页

编辑

useFirebaseAuth 挂钩

import React, { createContext, useContext, useEffect, useState } from 'react';
import { onAuthStateChanged, sendPasswordResetEmail, signOut, User } from 'firebase/auth';
import { auth } 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;
  loading: 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 [loading, setLoading] = useState(false);

  const { fetchUserSubscription } = useUserSubscription();

  useEffect(() => {
    const authStateChange = onAuthStateChanged(auth, async (user) => {
      setLoading(true);
      if (user) {
        setUser(user);
        const token = await user.getIdTokenResult();
        await fetchUserSubscription();
        const role = token.claims.role
          ? (token.claims.role as Role)
          : Role.GUEST;
        setRole(role);
      } else {
        setUser(null);
        setRole(Role.VISITOR);
        dispatch(clearUserSubscriptionData());
      }
      setLoading(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 () => {
    setLoading(true);
    try {
      await signOut(auth);
      setUser(null);
      setRole(Role.VISITOR);
      dispatch(clearUserSubscriptionData());
      router.push('/home');
    } catch (error) {
      console.error('Logout Error:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <FirebaseAuthContext.Provider
      value={{
        user,
        role,
        loading,
        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;
};

这是我通过身份验证并尝试访问经过身份验证的页面时的控制台日志(好)

Loading false AuthGuard.tsx:22:12
AuthGuard user null AuthGuard.tsx:23:12
Loading false AuthGuard.tsx:22:12
AuthGuard user null AuthGuard.tsx:23:12
Loading true AuthGuard.tsx:22:12
AuthGuard user Object { … }
AuthGuard.tsx:23:12
Loading true AuthGuard.tsx:22:12
AuthGuard user Object {  … }
AuthGuard.tsx:23:12
Loading false AuthGuard.tsx:22:12
AuthGuard user Object { … }
AuthGuard.tsx:23:12
Loading false AuthGuard.tsx:22:12
AuthGuard user Object { … }

当我未经身份验证并访问经过身份验证的页面时(不好,不断加载)

Loading false AuthGuard.tsx:22:12
AuthGuard user null AuthGuard.tsx:23:12
Loading false AuthGuard.tsx:22:12
AuthGuard user null AuthGuard.tsx:23:12
Loading false AuthGuard.tsx:22:12
AuthGuard user null
reactjs firebase next.js firebase-authentication
1个回答
0
投票

您的 AuthGuard 存在逻辑问题,当调用 useFirebaseAuth 挂钩时,首次渲染时加载始终为 true。所以useEffect会直接push到/home:

    if (!loading) {
      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)
      //   router.push('/home');
      // }
    }

试试这个:

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, loading } = useFirebaseAuth(); // Get user, role, and loading state from Firebase context
  const router = useRouter();
  const [isAuthorized, setIsAuthorized] = useState(false);

  useEffect(() => {
    // If the authentication state is still loading, do nothing
    if (loading) {
      return;
    }

    if (!user) {
      // If no user is logged in, redirect to the home page
      router.push('/home');
      setIsAuthorized(false);
    } else if (requiredPermission && role) {
      // If user and role exist, check for permissions
      if (!can(role, requiredPermission.action, requiredPermission.subject)) {
        // If the user doesn't have the required permissions, redirect to the dashboard
        router.push('/dashboard');
        setIsAuthorized(false);
      } else {
        setIsAuthorized(true);
      }
    }
  }, [loading, user, role, requiredPermission, router]);

  if (loading || !user || !isAuthorized) {
    return <Loader />;
  }

  return <>{children}</>;
}

不要忘记,您正在使用 AuthGuard 作为客户端组件,这几乎使其对于安全目的毫无用处,但我猜您只是将它用于一些 UI/UX 内容

© www.soinside.com 2019 - 2024. All rights reserved.