typescriptnext.jsnext.js13next-auth

Redirect and Sign In issues with NextAuth


In my NextJS, I am using NextAuth. I have sign in/sign up with credentials and Google set up. The problem I am having is of redirect. I have made this middleware:

export { default } from 'next-auth/middleware';

export const config = { matcher: ['/dashboard'] };

Now, I have this page in app/auth/register/page.tsx:

export default function AuthPage() {
    const [isSignIn, setIsSignIn] = useState(true);
    const [error, setError] = useState('');
    const router = useRouter();
    const { data: session, status } = useSession();

    useEffect(() => {
        if (status === 'authenticated') {
            router.push('/dashboard');
        }
    }, [status, router]);

    const handleGoogleSignIn = async () => {
        try {
            const result = await signIn('google', {
                callbackUrl: '/dashboard',
            });

            if (result?.error) {
                setError('Failed to sign in with Google');
            }
        } catch (err) {
            console.error(err);
            setError('Something went wrong with Google sign in');
        }
    };

    const handleSignIn = async (email: string, password: string) => {
        setError('');
        try {
            const result = await signIn(undefined, {
                email,
                password,
                callbackUrl: 'http://localhost:3000/dashboard',
            });

            if (result?.error) {
                setError('Invalid email or password');
            } else {
                router.push('/dashboard');
            }
        } catch (err) {
            console.log(err);
            setError('Something went wrong');
        }
    };

    const handleSignUp = async (
        email: string,
        password: string,
        firstName: string,
        lastName: string
    ) => {
        setError('');
        try {
            const res = await fetch('/api/auth/signup', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ email, password, firstName, lastName }),
            });

            const data = await res.json();

            if (!res.ok) {
                throw new Error(data.error || 'Something went wrong');
            }

            // Auto sign in after successful sign up
            const result = await signIn('credentials', {
                email,
                password,
                callbackUrl: 'http://localhost:3000/dashboard',
            });

            if (result?.error) {
                setError('Failed to sign in after sign up');
            } else {
                router.push('/dashboard');
            }
        } catch (err) {
            setError(err instanceof Error ? err.message : 'Something went wrong');
        }
    };

    return (
        <div className="flex-1 flex flex-col md:flex-row">
            <Link href="/" className="absolute top-6 left-6">
                <Image
                    src="/images/orbit-logo.png"
                    alt="Logo"
                    width={150}
                    height={150}
                    style={{ filter: 'invert(1) grayscale(1) contrast(100%)' }}
                />
            </Link>
            <div className="flex-1 md:w-1/2 bg-[#f3f3f5] flex flex-col justify-center items-center p-6 md:p-8">
                <div className="max-w-md">
                    <h2 className="text-4xl md:text-5xl font-bold text-[#171717] mb-6">
                        {isSignIn ? 'Welcome back' : 'Join Orbit today'}
                    </h2>
                    <p className="text-[#454545] text-lg mb-8">
                        {isSignIn
                            ? 'Sign in to access your dashboard and continue your journey.'
                            : 'Create an account and start exploring the universe of possibilities with Orbit.'}
                    </p>
                    <div className="hidden md:block">
                        <motion.div
                            initial={{ opacity: 0, y: 20 }}
                            animate={{ opacity: 1, y: 0 }}
                            transition={{ delay: 0.2 }}
                            className="bg-white p-6 rounded-xl shadow-lg"
                        >
                            <div className="flex items-start gap-4">
                                <div className="bg-[#fed835] p-2 rounded-full">
                                    <Orbit className="h-5 w-5 text-white" />
                                </div>
                                <div>
                                    <h3 className="font-medium text-[#171717]">
                                        Seamless Integration
                                    </h3>
                                    <p className="text-[#454545] text-sm">
                                        Connect with your favorite tools and services effortlessly.
                                    </p>
                                </div>
                            </div>
                        </motion.div>

                        <motion.div
                            initial={{ opacity: 0, y: 20 }}
                            animate={{ opacity: 1, y: 0 }}
                            transition={{ delay: 0.4 }}
                            className="bg-white p-6 rounded-xl shadow-lg mt-4"
                        >
                            <div className="flex items-start gap-4">
                                <div className="bg-[#304fff] p-2 rounded-full">
                                    <Orbit className="h-5 w-5 text-white" />
                                </div>
                                <div>
                                    <h3 className="font-medium text-[#171717]">
                                        Powerful Analytics
                                    </h3>
                                    <p className="text-[#454545] text-sm">
                                        Gain insights with our comprehensive analytics dashboard.
                                    </p>
                                </div>
                            </div>
                        </motion.div>
                    </div>
                </div>
            </div>

            {/* Right side - Form */}
            <div className="w-full md:w-1/2 flex justify-center items-center p-6 md:p-8 bg-white">
                <div className="w-full max-w-md">
                    <div className="flex justify-center mb-6">
                        <div className="inline-flex p-1 bg-[#f3f3f5] rounded-lg">
                            <button
                                onClick={() => setIsSignIn(true)}
                                className={`px-4 py-2 text-sm font-medium rounded-md transition-all duration-300 ${
                                    isSignIn
                                        ? 'bg-[#fed835] text-[#171717]'
                                        : 'bg-transparent text-[#454545] hover:text-[#171717]'
                                }`}
                            >
                                Sign In
                            </button>
                            <button
                                onClick={() => setIsSignIn(false)}
                                className={`px-4 py-2 text-sm font-medium rounded-md transition-all duration-300 ${
                                    !isSignIn
                                        ? 'bg-[#fed835] text-[#171717]'
                                        : 'bg-transparent text-[#454545] hover:text-[#171717]'
                                }`}
                            >
                                Sign Up
                            </button>
                        </div>
                    </div>

                    {error && (
                        <div className="bg-red-50 text-red-500 p-3 rounded-md text-sm mb-4">
                            {error}
                        </div>
                    )}

                    <AnimatePresence mode="wait">
                        <motion.div
                            key={isSignIn ? 'signin' : 'signup'}
                            initial={{ opacity: 0, x: isSignIn ? -20 : 20 }}
                            animate={{ opacity: 1, x: 0 }}
                            exit={{ opacity: 0, x: isSignIn ? 20 : -20 }}
                            transition={{ duration: 0.3 }}
                        >
                            {isSignIn ? (
                                <SignInForm
                                    onSubmit={handleSignIn}
                                    onGoogleSignIn={handleGoogleSignIn}
                                />
                            ) : (
                                <SignUpForm
                                    onSubmit={handleSignUp}
                                    onGoogleSignIn={handleGoogleSignIn}
                                />
                            )}
                        </motion.div>
                    </AnimatePresence>
                </div>
            </div>
        </div>
    );
}

The behaviour that I want is that it redirects me to /dashboard page however, what's happening is something like this:

  1. I go to the /auth/register page and then do Sign In with Google
  2. The URL changes to http://localhost:3000/auth/register?callbackUrl=http://localhost:3000/dashboard from http://localhost:3000/auth/register
  3. I am not redirected and the page loads again.
  4. The URL stays like that and it stays on the auth page.

I cannot figure out what I am doing wrong and what's causing this issue. Any help would be greatly appreciated.


Solution

  • await signIn('google', {
      callbackUrl: '/dashboard',
    });
    

    NextAuth redirects you to the Google consent screen, then back to the current page with a ?callbackUrl= query string

    if useSession() on that page doesn't re-trigger a redirect after auth, you're just stuck there.

    Instead of manually redirecting with router.push after checking session, just use the callbackUrl. Update your Google sign-in handler

    await signIn('google', { callbackUrl: '/dashboard' });

    in middleware.ts, handle redirect for already-authenticated users if they visit /auth/register

    // middleware.ts
    import { getToken } from 'next-auth/jwt';
    import { NextResponse } from 'next/server';
    
    export async function middleware(req) {
      const token = await getToken({ req });
      const { pathname } = req.nextUrl;
    
      // Redirect alredy logged-in users from auth pages
      if (token && pathname.startsWith('/auth')) {
        return NextResponse.redirect(new URL('/dashboard', req.url));
      }
    
      return NextResponse.next();
    }
    
    export const config = {
      matcher: ['/dashboard', '/auth/:path*'], // apply the middleware for register path
    };