我正在使用 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>
);
};
我错过了什么?
如果我理解正确,您有未经身份验证的用户访问
"/"
并被重定向到 "/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,
},
],
},
],
},
]);