Supabase Auth UI + Google 登录无法访问 AuthProvider

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

我正在使用 Vite + React Router v6 + Supabase Auth 组件和 Google OAuth 提供商。我仔细检查了 Google 凭据是否在 Supabase 配置中,

https://{my_supabase_id}.supabase.co/auth/v1/callback
在 Google 的客户端配置中配置,并且 Google 授权来源和 Supabase 站点 URL 均配置为我的 Vite url
http://localhost:5173

我可以在 Supabase 身份验证表中看到用户进行身份验证,但在我的前端,AuthProvider 永远不会点击我在创建会话时设置的调试器,因此它会路由回没有会话的登录屏幕。

这是我的 AuthProvider:

const AuthProvider = (props: AuthProviderProps) => {
  const [user, setUser] = useState<User | null>(null);
  const [session, setSession] = useState<Session | null>(null);
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    const { data: listener } = supabase.auth.onAuthStateChange(
      (_event, session) => {
        console.log('session onAuthStateChange: ', session);
        if (session) {
          debugger;
        }
        setSession(session);

        setUser(session?.user || null);
        setLoading(false);
      }
    );
    const setData = async () => {
      const {
        data: { session },
        error,
      } = await supabase.auth.getSession();
      console.log('session at setData:', session);
      if (session) {
        debugger;
      }

      if (error) {
        throw error;
      }
      setSession(session);
      setUser(session?.user || null);
      setLoading(false);
    };
    setData();

    return () => {
      listener?.subscription.unsubscribe();
    };
  }, [supabase.auth]);

  const value = {
    session,
    user,
  };

  return (
    <AuthContext.Provider value={value}>{props.children}</AuthContext.Provider>
  );
};

export const useAuth = () => {
  return useContext(AuthContext);
};

export default AuthProvider;

我有点不确定这个提供商到底应该去哪里,但这是我的路线:

const router = createBrowserRouter([
  {
    path: '/',
    element: (
      <AuthProvider>
        <Root />
      </AuthProvider>
    ),
    errorElement: <ErrorPage />,
    children: [
      {
        path: 'login',
        element: <Login />,
      },
      {
        path: '/',
        element: <ProtectedPage />,
        children: [
          {
            path: 'picks',
            element: <Picks />,
            loader: picksLoader,
          },
        ],
      },
    ],
  },
]);

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

这是我的

ProtectedPage
组件:

const ProtectedPage = () => {
  const { user, session } = useAuth();

  if (!session) {
    return <Navigate to='/login' replace />;
  }

  return <Layout />;
};

所以基本上,在

/
,它尝试加载我的
<ProtectedPage>
,然后如果会话为空,它会路由到
login

对于我的登录,我使用

<Auth />
中的
@supabase/auth-ui-react'
组件:

export const Login = () => {
  return (
    <div className='md:flex md:justify-center mb-6'>
      <div className='flex-col prose'>
        <h1>bp2</h1>
        <div className='items-center justify-center mx-12 h-screen'>
          <Auth
            supabaseClient={supabase}
            appearance={{ theme: ThemeSupa }}
            providers={['google']}
          />
        </div>
      </div>
    </div>
  );
};

我错过了什么?

oauth-2.0 react-router supabase supabase-js
1个回答
0
投票

问题

如果我理解正确,您有未经身份验证的用户访问

"/"
并被重定向到
"/login"
并跳出到您的身份验证服务。一旦经过身份验证,他们就会被重定向回您的应用程序
"/"
supabase.auth.onAuthStateChange
supabase.auth.getSession
处理程序尚未对此身份验证更改做出“反应”,因此
user
session
状态不会更新。在
ProtectedPage
上渲染的
"/"
然后将它们重定向回
"/login"
。这就是你陷入的循环。

解决方案

使用

loading
状态和/或从最初未定义的
session
状态开始,以帮助让 UI 等待至少初始会话检查完成。

const AuthProvider = (props: AuthProviderProps) => {
  const [user, setUser] = useState<User | null>(null);
  const [session, setSession] = useState<Session | null | undefined>(); // <-- undefined
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    const { data: listener } = supabase.auth.onAuthStateChange(
      (_event, session) => {
        console.log('session onAuthStateChange: ', session);

        setSession(session || null);
        setUser(session?.user || null);
      }
    );

    return () => {
      listener?.subscription.unsubscribe();
    };
  }, [supabase.auth]);

  const getSession = async () => {
    try {
      setLoading(true);

      const {
        data: { session },
        error,
      } = await supabase.auth.getSession();

      console.log('session at setData:', session);
      if (session) {
        debugger;
      }

      if (error) {
        throw error;
      }
      setSession(session || null);
      setUser(session?.user || null);
    } catch(error) {
      setSession(null);
      setUser(null);
    } finally {
      setLoading(false);
    }

  };

  const value = {
    loading,
    session,
    user,
    getSession,
  };

  return (
    <AuthContext.Provider value={value}>
      {props.children}
    </AuthContext.Provider>
  );
};
const ProtectedPage = () => {
  const { loading, session, getSession } = useAuth();

  useEffect(() => {
    if (!session) {
      getSession();
    }
  }, [session, getSession]);

  if (loading || session === undefined) {
    return null; // <-- or loading indicator/spinner/etc
  }

  return session ? <Outlet /> : <Navigate to='/login' replace />;
};
const router = createBrowserRouter([
  {
    path: '/',
    element: (
      <AuthProvider>
        <Root />
      </AuthProvider>
    ),
    errorElement: <ErrorPage />,
    children: [
      {
        path: 'login',
        element: <Login />,
      },
      {
        element: <ProtectedPage />,
        children: [
          {
            path: 'picks',
            element: <Picks />,
            loader: picksLoader,
          },
        ],
      },
    ],
  },
]);
© www.soinside.com 2019 - 2024. All rights reserved.