最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

reactjs - onAuthStateChange taking too much time to change it's state - Stack Overflow

programmeradmin3浏览0评论

So i am trying to sign in my user(a mentor in this case) using oAuth. I am using supabase for auth with a local postgres database and zustand for state management. The user is supposed to be navigated to a protected route called "/dashboard" after a successful login, the routes are protected by a wrapper that checks whether the auth state has isAuthenticated set to true or not which happens if a user is passed to it.

This is my signIn.tsx:

import { useState } from 'react';
import { Link } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Mail, Lock, Loader2 } from 'lucide-react';
import { supabase } from "@/lib/supabase";
import { useAuthStore } from '@/store/authStore';
import { FcGoogle } from "react-icons/fc";
import { FaLinkedin } from "react-icons/fa";

export default function SignIn() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const setUser = useAuthStore((state) => state.setUser);
  const navigate = useNavigate();
  
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setIsLoading(true);
  
    try {
      const { data } = await axios.post("http://localhost:5000/auth/signin", {
        email,
        password,
      });
  
      setUser(data.mentor); // Store mentor data in Zustand
      navigate("/dashboard");
    } catch (error: any) {
      alert(error.response?.data?.error || "Sign-in failed");
    } finally {
      setIsLoading(false);
    }
  };

  const handleOAuthLogin = async (provider: "google" | "linkedin_oidc") => {
    try {
      setIsLoading(true);
      const { data, error } = await supabase.auth.signInWithOAuth({ provider });
      console.log("oAuth : ", data);
      if (error) {
        alert(error.message);
        return;
      }
  
      // Wait for the OAuth process to complete
      supabase.auth.onAuthStateChange(async (event, session) => {
        if (event === "SIGNED_IN" && session?.user) {
          try {
            const { data: mentor } = await axios.get(`http://localhost:5000/auth/mentor/${session.user.id}`);
            setUser(mentor); // Update Zustand store
            navigate("/dashboard"); // Redirect to dashboard
          } catch (err) {
            console.error("Error fetching mentor profile:", err);
            alert("Failed to fetch user profile");
          }
        }
      });
    } catch (err) {
      console.error(err);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="min-h-screen flex items-center justify-center p-4">
      <div className="w-full max-w-md">
        <Card className="border-none shadow-xl">
          <CardHeader className="space-y-3">
            <CardTitle className="text-3xl font-bold text-center">Welcome back</CardTitle>
            <CardDescription className="text-center text-gray-600">
              Enter your email and password to access your account
            </CardDescription>
          </CardHeader>
          <form onSubmit={handleSubmit}>
            <CardContent className="space-y-4">
              <div className="space-y-2">
                <Label htmlFor="email">Email</Label>
                <div className="relative">
                  <Mail className="absolute left-3 top-3 h-5 w-5 text-gray-400" />
                  <Input 
                    id="email" 
                    type="email" 
                    value={email}
                    placeholder="[email protected]"
                    className="pl-10"
                    onChange={(e) => setEmail(e.target.value)}
                    required 
                  />
                </div>
              </div>
              <div className="space-y-2">
                <Label htmlFor="password">Password</Label>
                <div className="relative">
                  <Lock className="absolute left-3 top-3 h-5 w-5 text-gray-400" />
                  <Input 
                    id="password" 
                    type="password"
                    value={password}
                    onChange={(e) => setPassword(e.target.value)}
                    placeholder="Enter your password"
                    className="pl-10"
                    required 
                  />
                </div>
              </div>
              <Button 
                type="submit" 
                className="w-full bg-blue-600 hover:bg-blue-700"
                disabled={isLoading}
              >
                {isLoading ? (
                  <>
                    <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                    Signing in...
                  </>
                ) : (
                  'Sign In'
                )}
              </Button>
            </CardContent>
          </form>
          <CardFooter className="flex flex-col space-y-4 text-center">
            <Button 
              onClick={() => handleOAuthLogin("google")}
              className="w-full bg-white border border-gray-300 hover:bg-gray-100 flex items-center justify-center space-x-2"
              disabled={isLoading}
            >
              {isLoading ? (
                <>
                  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                  Logging in with Google...
                </>
              ) : (
                <>
                  <FcGoogle className="h-5 w-5" />
                  <span className="text-gray-700">Sign in with Google</span>
                </>
              )}
            </Button>
            <Button 
              onClick={() => handleOAuthLogin("linkedin_oidc")}
              className="w-full bg-blue-500 hover:bg-blue-600 text-white flex items-center justify-center space-x-2"
              disabled={isLoading}
            >
              {isLoading ? (
                <>
                  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                  Logging in with LinkedIn...
                </>
              ) : (
                <>
                  <FaLinkedin className="h-5 w-5" />
                  <span className="text-white">Sign in with LinkedIn</span>
                </>
              )}
            </Button>
            <div className="text-sm text-gray-600">
              Don't have an account?{' '}
              <Link to="/signup" className="font-semibold text-blue-600 hover:text-blue-700">
                Sign up
              </Link>
            </div>
          </CardFooter>
        </Card>
      </div>
    </div>
  );
}

In order to get the state updated in oAuth login i am waiting for the oAuthStateChange event to trigger and change to SIGNED_IN, then the api that is hit fetches the user's details from the local db based on the user id retrieved from the oAuth session:

router.get("/mentor/:id", async (req: any, res: any) => {
  const { id } = req.params;

  try {
    const mentor = await prisma.mentorProfile.findUnique({
      where: { mentor_id: id },
    });

    if (!mentor) return res.status(404).json({ error: "Mentor profile not found" });

    return res.status(200).json(mentor);
  } catch (err) {
    console.error("Error fetching mentor profile:", err);
    return res.status(500).json({ error: "Server error", details: err });
  }
});

The issue is, this oAuthStateChange seems to be taking too much time because what happens is:

  1. i select the email and everything, i get the accessToken in Application section of devTools but the state is null in authState
  2. it redirects me to "/" for some reason
  3. after a while(2-3 minutes) the state also updates and user is navigated to "/dashboard"

My whole sign in process works just fine when user is logging in via email and password and not a provider.

So i am trying to sign in my user(a mentor in this case) using oAuth. I am using supabase for auth with a local postgres database and zustand for state management. The user is supposed to be navigated to a protected route called "/dashboard" after a successful login, the routes are protected by a wrapper that checks whether the auth state has isAuthenticated set to true or not which happens if a user is passed to it.

This is my signIn.tsx:

import { useState } from 'react';
import { Link } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Mail, Lock, Loader2 } from 'lucide-react';
import { supabase } from "@/lib/supabase";
import { useAuthStore } from '@/store/authStore';
import { FcGoogle } from "react-icons/fc";
import { FaLinkedin } from "react-icons/fa";

export default function SignIn() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const setUser = useAuthStore((state) => state.setUser);
  const navigate = useNavigate();
  
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setIsLoading(true);
  
    try {
      const { data } = await axios.post("http://localhost:5000/auth/signin", {
        email,
        password,
      });
  
      setUser(data.mentor); // Store mentor data in Zustand
      navigate("/dashboard");
    } catch (error: any) {
      alert(error.response?.data?.error || "Sign-in failed");
    } finally {
      setIsLoading(false);
    }
  };

  const handleOAuthLogin = async (provider: "google" | "linkedin_oidc") => {
    try {
      setIsLoading(true);
      const { data, error } = await supabase.auth.signInWithOAuth({ provider });
      console.log("oAuth : ", data);
      if (error) {
        alert(error.message);
        return;
      }
  
      // Wait for the OAuth process to complete
      supabase.auth.onAuthStateChange(async (event, session) => {
        if (event === "SIGNED_IN" && session?.user) {
          try {
            const { data: mentor } = await axios.get(`http://localhost:5000/auth/mentor/${session.user.id}`);
            setUser(mentor); // Update Zustand store
            navigate("/dashboard"); // Redirect to dashboard
          } catch (err) {
            console.error("Error fetching mentor profile:", err);
            alert("Failed to fetch user profile");
          }
        }
      });
    } catch (err) {
      console.error(err);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="min-h-screen flex items-center justify-center p-4">
      <div className="w-full max-w-md">
        <Card className="border-none shadow-xl">
          <CardHeader className="space-y-3">
            <CardTitle className="text-3xl font-bold text-center">Welcome back</CardTitle>
            <CardDescription className="text-center text-gray-600">
              Enter your email and password to access your account
            </CardDescription>
          </CardHeader>
          <form onSubmit={handleSubmit}>
            <CardContent className="space-y-4">
              <div className="space-y-2">
                <Label htmlFor="email">Email</Label>
                <div className="relative">
                  <Mail className="absolute left-3 top-3 h-5 w-5 text-gray-400" />
                  <Input 
                    id="email" 
                    type="email" 
                    value={email}
                    placeholder="[email protected]"
                    className="pl-10"
                    onChange={(e) => setEmail(e.target.value)}
                    required 
                  />
                </div>
              </div>
              <div className="space-y-2">
                <Label htmlFor="password">Password</Label>
                <div className="relative">
                  <Lock className="absolute left-3 top-3 h-5 w-5 text-gray-400" />
                  <Input 
                    id="password" 
                    type="password"
                    value={password}
                    onChange={(e) => setPassword(e.target.value)}
                    placeholder="Enter your password"
                    className="pl-10"
                    required 
                  />
                </div>
              </div>
              <Button 
                type="submit" 
                className="w-full bg-blue-600 hover:bg-blue-700"
                disabled={isLoading}
              >
                {isLoading ? (
                  <>
                    <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                    Signing in...
                  </>
                ) : (
                  'Sign In'
                )}
              </Button>
            </CardContent>
          </form>
          <CardFooter className="flex flex-col space-y-4 text-center">
            <Button 
              onClick={() => handleOAuthLogin("google")}
              className="w-full bg-white border border-gray-300 hover:bg-gray-100 flex items-center justify-center space-x-2"
              disabled={isLoading}
            >
              {isLoading ? (
                <>
                  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                  Logging in with Google...
                </>
              ) : (
                <>
                  <FcGoogle className="h-5 w-5" />
                  <span className="text-gray-700">Sign in with Google</span>
                </>
              )}
            </Button>
            <Button 
              onClick={() => handleOAuthLogin("linkedin_oidc")}
              className="w-full bg-blue-500 hover:bg-blue-600 text-white flex items-center justify-center space-x-2"
              disabled={isLoading}
            >
              {isLoading ? (
                <>
                  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                  Logging in with LinkedIn...
                </>
              ) : (
                <>
                  <FaLinkedin className="h-5 w-5" />
                  <span className="text-white">Sign in with LinkedIn</span>
                </>
              )}
            </Button>
            <div className="text-sm text-gray-600">
              Don't have an account?{' '}
              <Link to="/signup" className="font-semibold text-blue-600 hover:text-blue-700">
                Sign up
              </Link>
            </div>
          </CardFooter>
        </Card>
      </div>
    </div>
  );
}

In order to get the state updated in oAuth login i am waiting for the oAuthStateChange event to trigger and change to SIGNED_IN, then the api that is hit fetches the user's details from the local db based on the user id retrieved from the oAuth session:

router.get("/mentor/:id", async (req: any, res: any) => {
  const { id } = req.params;

  try {
    const mentor = await prisma.mentorProfile.findUnique({
      where: { mentor_id: id },
    });

    if (!mentor) return res.status(404).json({ error: "Mentor profile not found" });

    return res.status(200).json(mentor);
  } catch (err) {
    console.error("Error fetching mentor profile:", err);
    return res.status(500).json({ error: "Server error", details: err });
  }
});

The issue is, this oAuthStateChange seems to be taking too much time because what happens is:

  1. i select the email and everything, i get the accessToken in Application section of devTools but the state is null in authState
  2. it redirects me to "/" for some reason
  3. after a while(2-3 minutes) the state also updates and user is navigated to "/dashboard"

My whole sign in process works just fine when user is logging in via email and password and not a provider.

Share Improve this question asked Mar 18 at 8:12 kb456kb456 13 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

Fixing OAuth Authentication Flow in React with Supabase The main problem seems to be in how you're handling the OAuth flow and authentication state changes. Here's what I see:

In your handleOAuthLogin function, you're setting up the OAuth state change listener after initiating the sign-in process. This is problematic because:

The auth state change might happen before your listener is registered You're not cleaning up the listener, which can cause multiple listeners to accumulate

The redirect to "/" is likely happening because the OAuth flow completes, but your application hasn't yet processed the authenticated state.

Here's how I would fix this:

// Set up the auth state listener once, outside of the handleOAuthLogin function // Add this in a useEffect at the component level

supabase.auth.onAuthStateChange(async (event, session) => {
    if (event === "SIGNED_IN" && session?.user) {
      try {
        const { data: mentor } = await axios.get(`http://localhost:5000/auth/mentor/${session.user.id}`);
        setUser(mentor);
        navigate("/dashboard");
      } catch (err) {
        console.error("Error fetching mentor profile:", err);
        alert("Failed to fetch user profile");
      }
    }   });

  // Clean up the listener when component unmounts   return () => {
    authListener.subscription.unsubscribe();   }; }, []);`

// Then modify your handleOAuthLogin function to be simpler

"linkedin_oidc") => {   try {
    setIsLoading(true);
    const { error } = await supabase.auth.signInWithOAuth({ 
      provider,
      options: {
        redirectTo: window.location.origin // Ensure it redirects back to your app
      }
    });
    
    if (error) {
      alert(error.message);
    }   } catch (err) {
    console.error(err);
    alert("Authentication failed");   } finally {
    setIsLoading(false);   } };`


Another issue might be that you're not handling the OAuth redirect
properly. The OAuth flow typically redirects the user away from your
application and then back after authentication. When the user returns,
you need to check if they're already authenticated. Add this to your
component:

> ``` 
> 
> component mounts   const checkUser = async () => {
>     const { data: { session } } = await supabase.auth.getSession();
>     if (session?.user) {
>       try {
>         const { data: mentor } = await axios.get(`http://localhost:5000/auth/mentor/${session.user.id}`);
>         setUser(mentor);
>         navigate("/dashboard");
>       } catch (err) {
>         console.error("Error fetching mentor profile:", err);
>       }
>     }   };
>      checkUser(); }, []); `useEffect(() => {   // Check if user is already authenticated when

This approach should:

Set up the auth state listener once when the component mounts Check if
the user is already authenticated when they land on the page (which
handles the redirect back case) Properly clean up listeners to prevent
memory leaks and duplicate event handlers

Don't fet to import useEffect at the top of your file:

` import { useState, useEffect } from 'react'; `
发布评论

评论列表(0)

  1. 暂无评论