Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

no redirection after successful login #10016

Open
78raoul78 opened this issue Feb 13, 2024 · 26 comments
Open

no redirection after successful login #10016

78raoul78 opened this issue Feb 13, 2024 · 26 comments
Labels
bug Something isn't working triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.

Comments

@78raoul78
Copy link

Environment

System:
OS: macOS 14.1.2
CPU: (8) arm64 Apple M2
Memory: 489.31 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 20.7.0 - /opt/homebrew/bin/node
Yarn: 1.22.21 - /opt/homebrew/bin/yarn
npm: 10.1.0 - /opt/homebrew/bin/npm
pnpm: 8.14.0 - /opt/homebrew/bin/pnpm
Browsers:
Chrome: 121.0.6167.160
Safari: 17.1.2
npmPackages:
next: 14.1.0 => 14.1.0
react: 18.2.0 => 18.2.0

Reproduction URL

https://github.com/78raoul78/ui

Describe the issue

After a successful signIn, I'm not getting redirected to my home page even though the callbackUrl is correctly set.

How to reproduce

  1. Checkout the repository
  2. Create an .env file in apps/www and paste inside the content from app/www/.env.example (put your own AUTH_SECRET and the url of your POSTGRES_URL)
  3. Go to apps/www and run pnpm install
  4. then pnpm run seed
  5. then pnpm build
  6. then pnpm dev
  7. Go to http://localhost:3001
  8. You should be redirected to the signIn page http://localhost:3001/signIn?callbackUrl=http%3A%2F%2Flocalhost%3A3001%2F
  9. submit a valid account credentials : [email protected] / 123456
  10. here is the issue : instead of being redirected to http://localhost:3001, you're being redirected to http://localhost:3001/signIn?callbackUrl=http%3A%2F%2Flocalhost%3A3001%2F , whats the interest of the callbackUrl if I'm not redirected to it 🤔

Expected behavior

After a successful login, I'm expecting to be redirected to the initial url (the one before the redirection) which is localhost:3001

@78raoul78 78raoul78 added bug Something isn't working triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. labels Feb 13, 2024
@nauvalyusufaddairy
Copy link

There are a lot of bugs in version 5. It better try to create an auth flow from scratch or rollback to v4.

@schilffarth
Copy link

I am not an official maintainer, but I think you have mistaken callback url with redirect url.
The callback is for auth, you need to do redirect after you are logged in.
Here's my code:

import { signIn } from "next-auth/react";
import { useRouter } from "next13-progressbar";
import { toast } from "sonner";


export default function Login() {
  const router = useRouter();
  

 const onSubmit = async (values: z.infer<typeof formSchema>) => {
    setIsLoading(true);
    const { email, password } = values;
    try {
      const resp = await signIn("credentials", {
        email,
        password,
        redirect: false,
      });
      if (!resp || resp.error) {
        toast.error(`Login in failed: ${resp?.error}` || "unkonwn error");
        return;
      }
      toast.success("woohoo");
      const redirect = searchParams.get("redirect");
      if (redirect) {
        router.push(redirect);
      } else {
        router.push("/home");
      }
    } catch (error) {
      toast.error(`Login in failed: ${error}` || "unkonwn error");
      return;
    } finally {
      setIsLoading(false);
    }
  };

...

I may be wrong. But you can give this a try.

With this could you probably have /had a similar issue to mine:
After successful login, the app's session isn't updated. The session in my root layout.tsx is logging the correct session data in server side rendering, but useSession on client side remains undefined.
A manual page refresh will fix this issue, client side useSession gets uptodate session data.

Anyone familiar with this issue? I do not want to perform a whole page refresh upon login / logout. This sucks.

@schilffarth
Copy link

I think first of all you shouldnt use getSession() in layout.
And the client side session should be updated, I recently did a twitter login and it works:

import { signIn, signOut, useSession } from "next-auth/react";

export default function ConnectAccount() {
  const { data, status } = useSession();
   
 if (status === "authenticated") {
    return (
      <>
        <Button
          className="w-full"
          variant={"outline"}
          onClick={() => signOut()}
        >
         xxx
       </>);
}
else {
   return (
    <Button
      className="w-full"
      variant={"outline"}
      onClick={() => signIn("twitter")}
    >
      <Image src="/icons/X_logo.svg" width={20} height={20} alt="X" />
    </Button>
  );
}

and this signIn works perfectly good.
If you could share your code We can probably figure out something.

My session is initialized / fetched in the root layout with const session = await auth(), using next auth beta v11 right now.

This session is passed to the SessionProvider.

In i.e. the AppBar / NavBar, where I have a user button or a login button based on session status, I retrieve the session with useSession().

When I log the session in my root layout.tsx, it logs correctly the uptodate sssiondata on server side in my ide console.
But a log in the AppBar where I use useSession remains undefined, even if the root layout logs the correct values.

Upon manual page refresh (or window.location.refresh) the session is passed down correctly and my AppBar useSession returns the proper data.

Recently I discovered that the same issue exists for the auth logout. If redirect is set to false, the logout doesn't actually happen on client side and the session isn't updated until manual page refresh.

The whole situation is all about these annoying page refreshes. I do not wanna reload the entire app upon login/logout, i just want the ui to represent the actual session as anyone would expect.

I will share code later when I get home, unless you have any ideas based on this description already?

@schilffarth
Copy link

I actually finally made it work thanks to another very recent github issue (cannot find it anymore tho lol)

The underlying issue was that next-auth's session provider doesn't update the session passed from auth() in client side, it only happens upon page refresh.

The solution was to build a custom SessionProvider that utilizes getSession() instead of auth() and passing it as a state onto next-auth's SessionProvider:

"use client";

import { Session } from "next-auth";
import {
    SessionProvider as NextSessionProvider,
    getSession
} from "next-auth/react";
import { usePathname } from "next/navigation";
import {
    ReactNode,
    useCallback,
    useEffect,
    useState
} from "react";

// Retrieve user session for the app's session context
export default function SessionProvider({
    children
}: {
    children: ReactNode;
}) {
    const [ session, setSession ] = useState<Session | null>(null);
    const pathName = usePathname();

    const fetchSession = useCallback(async () => {
        try {
            const sessionData = await getSession();
            setSession(sessionData);
        } catch (error) {
            setSession(null);

            if (process.env.NODE_ENV === "development") {
                console.error(error);
            }
        }
    }, []);

    useEffect(() => {
        fetchSession().finally();
    }, [fetchSession, pathName]);

    return (
        <NextSessionProvider session={session}>
            {children}
        </NextSessionProvider>
    );
}

Now I can use useSession() from next-auth just normally and I am returned the up-to-date session data after login. Now, I do not have to reload the entire app upon login / logout!!!

I am very happy as I finally figured this out, it's been plagueing me for almost 2 months by now.

@umar-brackets
Copy link

I am facing something like you
The code i have provided works perfectly in development, when i signup it redirects to welcome page and clicking on logo on the header where path is ' / ' it goes to home with no external reload but when i create build and start build, after signup/create new user it redirects to welcome page but clicking on logo with path ' / ' it redirects to login page instead of home untill i reload the page. And it just happens in the production after creating build. Please provide solution for it. Thanks

This is my root layout code

export default function RootLayout({ children, session }: IProps) {
const pathname = usePathname()

const handleSignOut = async () => {
await signOut()
}

return (


<body
className={bg-[#EFF5F6] ${ pathname?.includes('/visit') ? 'h-[100vh]' : '' }}
>



{children}

        {/* SignOut */}
        {pathname === '/account/profile' && (
          <div className="flex h-40 justify-center">
            <Button
              className="w-44 bg-[#DCE2E4] py-6 text-[#54595F] hover:bg-[#c4cacc]"
              onClick={() => handleSignOut()}
            >
              Logout
            </Button>
          </div>
        )}
        {/* Delete Dependent */}
        {pathname?.includes('/account/dependents/bio') && (
          <div className="flex h-40 justify-center">
            <Button className="w-52 bg-[#DCE2E4] py-6 text-[#54595F] hover:bg-[#c4cacc]">
              Delete Dependent
            </Button>
          </div>
        )}
        {/* <div className="hidden md:block"> */}
        <Footer />
        {/* </div> */}
      </SessionProvider>
    </body>
  </html>
</Providers>

)
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////
and this is the middleware code where i am checking the session and accordingly redirecting to different pages

import { NextResponse } from 'next/server'
import { getSession } from 'next-auth/react'

export async function middleware(request: any) {
const path = request.nextUrl.pathname
// const session = await getSession({ req: request })
const cookie = request.headers.get('cookie')
const session: any = cookie
? await getSession({ req: { headers: { cookie } } as any })
: null

// Check if session exists or not
if (!session) {
// If not authenticated, check if the user is trying to access /login or /signup
if (path === '/login' || path.includes('/signup') || path === '/forgot') {
// Allow access to /login and /signup for unauthenticated users
return NextResponse.next()
}
// Redirect to the login page for any other unauthenticated requests
return NextResponse.redirect(new URL(/login, request.url))
}
// If session exists and trying to access /login or /signup, redirect to the home page
if (path === '/login' || path.includes('/signup') || path === '/forgot') {
return NextResponse.redirect(new URL(/, request.url))
}
// Allow access to any other routes during the session
return NextResponse.next()
}

export const config = {
unstable_allowDynamic: ['/node_modules/@babel//**/.js'],
matcher: [
'/',
'/login',
'/signup',
'/forgot',
'/signup/account',
'/visit/:path
',
'/add-dependent',
'/add-pharmacy',
'/add-credit-card',
'/account',
'/account/:path*',
'/follow-up',

@18601673727
Copy link

Well I think by their documentation:

  
You will likely not need useSession if you are using the Next.js App Router.
  

So we are not supposed to useSession or getSession at the first place, but to router.reload() after each signIn(), to allow our const session = await auth() doing its job properly, let alone confuse ourselves even more with useEffect + Context approaches..

Sanchit-Warang added a commit to Sanchit-Warang/Stream-Scape-2 that referenced this issue May 27, 2024
Session change was not visible on client as Auth.js login and signout stopped refreshing the page
solution to implement Session provider with getSession() rather than auth()
refer: nextauthjs/next-auth#10016 (comment)
also fixed Skeleton Lint issues in Loading 🚨
@lespons
Copy link

lespons commented Jun 1, 2024

with server action I use

'use server';

import { signIn } from '@/lib/auth';
import { AuthError } from 'next-auth';
import { redirect } from 'next/navigation';

export async function authenticate(_: string | undefined, formData: FormData) {
  try {
    await signIn('credentials', formData);
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case 'CredentialsSignin':
          return 'Invalid credentials.';
        default:
          return 'Something went wrong.';
      }
    }
    throw error;
  } finally {
    redirect('/'); // or your pathname
  }
}

and client

...
  const [errorMessage, dispatch] = useFormState(authenticate, undefined);

  return (
    <form action={dispatch} className="..">
...

@weijyun9008
Copy link

with server action I use

'use server';

import { signIn } from '@/lib/auth';
import { AuthError } from 'next-auth';
import { redirect } from 'next/navigation';

export async function authenticate(_: string | undefined, formData: FormData) {
  try {
    await signIn('credentials', formData);
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case 'CredentialsSignin':
          return 'Invalid credentials.';
        default:
          return 'Something went wrong.';
      }
    }
    throw error;
  } finally {
    redirect('/'); // or your pathname
  }
}

and client

...
  const [errorMessage, dispatch] = useFormState(authenticate, undefined);

  return (
    <form action={dispatch} className="..">
...

Placing redirect('/') inside the finally block causes a redirection in all cases, even when an error occurs, preventing the errorMessage from being displayed on the form. To fix this, you can add a condition check:

export async function authenticate(
  prevState: string | undefined,
  formData: FormData,
) {
  let errorOccurred = false;
  try {
    await signIn('credentials', formData);
  } catch (error) {
    if (error instanceof AuthError) {
      errorOccurred = true;
      switch (error.type) {
        case 'CredentialsSignin':
          return 'Invalid credentials.';
        default:
          return 'Something went wrong.';
      }
    }
    throw error;
  } finally {
    if (!errorOccurred) {
      redirect('/your-pathname');
    }
  }
}

Note that errorOccurred = true; is placed inside the if clause because only AuthError is considered a valid error in this context.

@adityav477

This comment has been minimized.

@rhiskey
Copy link

rhiskey commented Jun 18, 2024

I actually finally made it work thanks to another very recent github issue (cannot find it anymore tho lol)

The underlying issue was that next-auth's session provider doesn't update the session passed from auth() in client side, it only happens upon page refresh.

The solution was to build a custom SessionProvider that utilizes getSession() instead of auth() and passing it as a state onto next-auth's SessionProvider:

"use client";

import { Session } from "next-auth";
import {
    SessionProvider as NextSessionProvider,
    getSession
} from "next-auth/react";
import { usePathname } from "next/navigation";
import {
    ReactNode,
    useCallback,
    useEffect,
    useState
} from "react";

// Retrieve user session for the app's session context
export default function SessionProvider({
    children
}: {
    children: ReactNode;
}) {
    const [ session, setSession ] = useState<Session | null>(null);
    const pathName = usePathname();

    const fetchSession = useCallback(async () => {
        try {
            const sessionData = await getSession();
            setSession(sessionData);
        } catch (error) {
            setSession(null);

            if (process.env.NODE_ENV === "development") {
                console.error(error);
            }
        }
    }, []);

    useEffect(() => {
        fetchSession().finally();
    }, [fetchSession, pathName]);

    return (
        <NextSessionProvider session={session}>
            {children}
        </NextSessionProvider>
    );
}

Now I can use useSession() from next-auth just normally and I am returned the up-to-date session data after login. Now, I do not have to reload the entire app upon login / logout!!!

I am very happy as I finally figured this out, it's been plagueing me for almost 2 months by now.

It works, thanks! Saved my life!

@rhiskey
Copy link

rhiskey commented Jun 18, 2024

I found solution without modifying SessionProvider.
You have to properly split codebase to server/client part. According Next 14 App Router guidelines.

I use

    "next-auth": "^5.0.0-beta.19",
    "next": "^14.2.3",

You have to properly split codebase to server/client part. According to Next 14 App Router guidelines.

So, guys, I got your back!

Follow these steps

1. Create current-session.ts in lib folder.

.
├── ...
├── app                  
│   ├── components     # here you can store client side components
└── lib

lib/current-session.ts

import { auth } from "@/auth";

/**
 * Retrieves the current user from the authentication session.
 *
 * @return {Promise<User | undefined>} The current user if available, otherwise undefined.
 */
export const currentUser = async () => {
  const session = await auth();

  return session?.user;
};

This is our server side session user. Use in any component that supposed to work as server component (by default it is). For example layout.tsx or page.tsx

2. Get user in any server component then pass to client component

Important

For example, i use <Navbar/> in my layout.tsx.
I have In Navbar user button AuthButton , so to actually update state of this button (load avatar, nickname, email) after sign-in or sign-up without refreshing page simply pass user to client component (in this case AuthButton).

app/_components/Navbar.tsx. I prefer to store server-side components in suffix-like folders _components

import {currentUser } from "@/lib/current-session";

export const Navbar = async () => {
  // Get user from server side in layout (for example navbar)
  const user = await currentUser();

  return (
     <AuthButton user={user} />
  );
};

3. Create AuthButton.tsx client component and get data from parent server component (Navbar.tsx).

app/components/AuthButton.tsx

"use client";

interface AuthButtonProps {
  user?: User
}

export const AuthButton = ({
  user,
}: AuthButtonProps) => {
  if (user) {
      return (
        <div>
            <span>{user.image}</span>
            <p>{user.email}</p>
        </div>
      );
  }
  return (
     <p>Not logged in</p>
  );
};

Where user is type of default User. You can extend this via database model

type User = {
    id: string;
    image: string;
    email: string;
    // etc...
}

Hope it helps, please write back if this works for you @weijyun9008 @lespons @78raoul78

@bonface221
Copy link

I don't get it mine goes to the home route when I have clearly specified to rediret to the '/admin' and only after refresh do I get the correct behaviour

export default auth((req) => {
const { nextUrl } = req;
const isLoggedIn = !!req.auth;

const isApiAuthRoute = nextUrl.pathname.startsWith(apiPrefix);
const isPublicRoute = publicRoutes.includes(nextUrl.pathname);
const isAuthRoute = authRoutes.includes(nextUrl.pathname);
const isAdminRoute = nextUrl.pathname.startsWith("/admin");

// TODO: remove this on prod
// return void 0;

if (isApiAuthRoute || isAuthRoute || isPublicRoute) {
return void 0;
}
if (isAuthRoute) {
if (isLoggedIn) {
return Response.redirect(new URL("/admin", nextUrl));
}
return void 0;
}

if (!isAdminRoute && isLoggedIn) {
return Response.redirect(new URL("/admin", nextUrl));
}

if (isAdminRoute && isLoggedIn) {
return NextResponse.next();
}

return Response.redirect(new URL("/auth/login", nextUrl));
});

// Optionally, don't invoke Middleware on some paths
export const config = {
matcher: ["/((?!.+\.[\w]+$|_next).)", "/(api|trpc)(.)"],
};

@wzrdx
Copy link

wzrdx commented Aug 7, 2024

I actually finally made it work thanks to another very recent github issue (cannot find it anymore tho lol)

The underlying issue was that next-auth's session provider doesn't update the session passed from auth() in client side, it only happens upon page refresh.

The solution was to build a custom SessionProvider that utilizes getSession() instead of auth() and passing it as a state onto next-auth's SessionProvider:

"use client";

import { Session } from "next-auth";
import {
    SessionProvider as NextSessionProvider,
    getSession
} from "next-auth/react";
import { usePathname } from "next/navigation";
import {
    ReactNode,
    useCallback,
    useEffect,
    useState
} from "react";

// Retrieve user session for the app's session context
export default function SessionProvider({
    children
}: {
    children: ReactNode;
}) {
    const [ session, setSession ] = useState<Session | null>(null);
    const pathName = usePathname();

    const fetchSession = useCallback(async () => {
        try {
            const sessionData = await getSession();
            setSession(sessionData);
        } catch (error) {
            setSession(null);

            if (process.env.NODE_ENV === "development") {
                console.error(error);
            }
        }
    }, []);

    useEffect(() => {
        fetchSession().finally();
    }, [fetchSession, pathName]);

    return (
        <NextSessionProvider session={session}>
            {children}
        </NextSessionProvider>
    );
}

Now I can use useSession() from next-auth just normally and I am returned the up-to-date session data after login. Now, I do not have to reload the entire app upon login / logout!!!

I am very happy as I finally figured this out, it's been plagueing me for almost 2 months by now.

Using this component, the session is not even updated after reloading lol.

This cannot possibly work as it does the exact same thing useSession().

@wzrdx
Copy link

wzrdx commented Aug 7, 2024

I found solution without modifying SessionProvider. You have to properly split codebase to server/client part. According Next 14 App Router guidelines.

I use

    "next-auth": "^5.0.0-beta.19",
    "next": "^14.2.3",

You have to properly split codebase to server/client part. According to Next 14 App Router guidelines.

So, guys, I got your back!

Follow these steps

1. Create current-session.ts in lib folder.

.
├── ...
├── app                  
│   ├── components     # here you can store client side components
└── lib

lib/current-session.ts

import { auth } from "@/auth";

/**
 * Retrieves the current user from the authentication session.
 *
 * @return {Promise<User | undefined>} The current user if available, otherwise undefined.
 */
export const currentUser = async () => {
  const session = await auth();

  return session?.user;
};

This is our server side session user. Use in any component that supposed to work as server component (by default it is). For example layout.tsx or page.tsx

2. Get user in any server component then pass to client component

Important

For example, i use <Navbar/> in my layout.tsx. I have In Navbar user button AuthButton , so to actually update state of this button (load avatar, nickname, email) after sign-in or sign-up without refreshing page simply pass user to client component (in this case AuthButton).

app/_components/Navbar.tsx. I prefer to store server-side components in suffix-like folders _components

import {currentUser } from "@/lib/current-session";

export const Navbar = async () => {
  // Get user from server side in layout (for example navbar)
  const user = await currentUser();

  return (
     <AuthButton user={user} />
  );
};

3. Create AuthButton.tsx client component and get data from parent server component (Navbar.tsx).

app/components/AuthButton.tsx

"use client";

interface AuthButtonProps {
  user?: User
}

export const AuthButton = ({
  user,
}: AuthButtonProps) => {
  if (user) {
      return (
        <div>
            <span>{user.image}</span>
            <p>{user.email}</p>
        </div>
      );
  }
  return (
     <p>Not logged in</p>
  );
};

Where user is type of default User. You can extend this via database model

type User = {
    id: string;
    image: string;
    email: string;
    // etc...
}

Hope it helps, please write back if this works for you @weijyun9008 @lespons @78raoul78

Doesn't this cause your whole app to be dynamically rendered, instead of having static & dynamic pages?

@bartoszhernas
Copy link

I had the same issue with custom login page, adding this to custom login page fixed the issue for me (or hid it)

  const session = await auth();
  if (session) {
    redirect(searchParams?.callbackUrl ? decodeURIComponent(searchParams.callbackUrl) : '/');
  }

@ritik3236
Copy link

I solved issue in server action by adding isRedirectError, in my actions/auth.ts file

export async function doLogin(formData: z.infer<typeof signInSchema>, callbackUrl = DEFAULT_LOGIN_REDIRECT) {
    try {
 
        ....
        await signIn('credentials', {
            redirectTo: callbackUrl,
            email: parsedCredentials.data.email,
            password: parsedCredentials.data.password,
            remember: parsedCredentials.data.remember,
        });
    } catch (e: unknown) {
        if (isRedirectError(e)) throw e;

        .... 
        const nextError = e as AuthError;
        const error = nextError.cause?.err as CustomError;
 
        // Handle unexpected CustomError types
        return {success: false, error: {message: 'An unexpected error occurred.'}};
    }
}

@Linger7
Copy link

Linger7 commented Sep 13, 2024

Still facing this issue with the latest version of NextAuth and NextJs

@raghav-kokotree
Copy link

Here’s the workaround I implemented for handling the redirection along with the callback URL after login. I would appreciate feedback from the community and the developers of NextAuth.js to confirm whether this is a good approach.

On the Login Page (Page)

import { useSearchParams } from 'next/navigation';

export default function LoginPage() {
  const searchParams = useSearchParams();
  const callbackUrl = searchParams.get('callbackUrl');

  return (
    <main className="flex items-center justify-center md:h-screen">
       {/* Other components */}
       <LoginForm callbackUrl={callbackUrl} />
       {/* Other components */}
    </main>
  );
}

Passing the callbackUrl to the Login Form (Component)

export default function LoginForm({
  callbackUrl,
}: {
  callbackUrl: string | null;
}) {
  const [errorMessage, formAction, isPending] = useActionState(
    (prevState: string | undefined, formData: FormData) =>
      myLogin(prevState, formData, callbackUrl),
    undefined,
  );

  return (
    <form action={formAction}>
      {/* Form fields go here */}
    </form>
  );
}

Handling the Login Action (Server Component)

export async function myLogin(
  prevState: string | undefined,
  formData: FormData,
  callbackUrl: string | null,
) {
  try {
    await signIn('credentials', formData);
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case 'CredentialsSignin':
          return 'Invalid credentials.';
        default:
          return 'Something went wrong.';
      }
    }
    throw error;
  } finally {
    const decodedCallbackUrl = callbackUrl
      ? decodeURIComponent(callbackUrl)
      : '/';
    redirect(decodedCallbackUrl);
  }
}

Redirect After Login

With this code, after a successful login, the user will be redirected to the callbackUrl:

const decodedCallbackUrl = callbackUrl
  ? decodeURIComponent(callbackUrl)
  : '/';
redirect(decodedCallbackUrl);

@Peaverin
Copy link

Peaverin commented Oct 4, 2024

For the record, still facing this issue as of today

"next": "14.2.14",
"next-auth": "^5.0.0-beta.22",

@lespons workaround (+ @weijyun9008 correction) worked for me

@fqf
Copy link

fqf commented Oct 13, 2024

I'm using [email protected] and in my case there wasn't redirection because of next_redirect error was occured. The reason was in catch block where I didn't throw error object:

try {
  await signIn(......);
} catch (error) {
  return { success: false, message: "auth error" };
}

Note, you should have this part in the end of catch block:

catch (error) {
  ................
  throw error;
} 

@jabedzaman
Copy link
Contributor

I'm using [email protected] and in my case there wasn't redirection because of next_redirect error was occured. The reason was in catch block where I didn't throw error object:

try {
  await signIn(......);
} catch (error) {
  return { success: false, message: "auth error" };
}

Note, you should have this part in the end of catch block:

catch (error) {
  ................
  throw error;
} 

even with this I am still facing this error

@1saifj
Copy link

1saifj commented Oct 28, 2024

for client complements ONLY you should use "useRouter" hook, it will save the day:

  const onSubmit = async (values: z.infer<typeof LoginSchema>) => {
    setError("");
    setSuccess("");
    setIsLoading(true);
    const res = await signIn("custom_credentials", {
      redirect: false,
      redirectTo: '/',
      email: values.email,
      password: values.password,
    });
    setIsLoading(false);
    if (res?.error) {
     if (res?.error === "CredentialsSignin") {
      setError("كلمة المرور أو البريد الإلكتروني غير صحيح");
     }
    } else {
      setSuccess("تم تسجيل الدخول بنجاح");
      router.push('/')
    }
  };
  };

so this is the escape

   router.push('/')

@cschroeter
Copy link

  signIn('credentials', {
    username: formData.get('username'),
    password: formData.get('password'),
    redirectTo: formData.get('redierctTo')?.toString() ?? '/',
  })

@decoderid
Copy link

NextAuthProvider

'use client'

import type { Session } from "next-auth"
import { SessionProvider } from "next-auth/react"

export default function NextAuthSessionProvider({ children, session }: { children: React.ReactNode, session: Session | null }) {
    return <SessionProvider session={session}>{children}</SessionProvider>
}

Actions

'use server';
 
import { signIn } from '@/auth';
import { AuthError } from 'next-auth';
 
export async function authenticate(
  prevState: string | undefined,
  formData: FormData,
) {
  try {
    const username = formData.get('username')
    const password = formData.get('password')
    const callbackUrl = formData.get('callbackUrl')
    await signIn('credentials', {
      username,
      password,
      redirect: true,
      redirectTo: String(callbackUrl)
    })
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case 'CredentialsSignin':
          return 'Invalid credentials.';
        default:
          return 'Something went wrong.';
      }
    }
    throw error;
  }
}

LoginForm

'use client'

import { useActionState } from "react"
import { authenticate } from "@/app/lib/actions"
import { useSearchParams } from "next/navigation"

export default function Page() {
    const [errorMessage, formAction, isPending] = useActionState(
        authenticate,
        undefined
    )

    const search = useSearchParams()
    const callbackUrl = search.get('callbackUrl') ?? '/'

    return (
        <form action={formAction} className="grid grid-cols-1 gap-4">
            <input type="text" name="username" defaultValue="super" />
            <input type="text" name="password" defaultValue="123456" />
            <input type="hidden" name="callbackUrl" defaultValue={callbackUrl} />
            <button type="submit">Login</button>
        </form>
    )
}

I had to refresh page

Screenbits.2024-12-13_183607.mp4

Npm Package

├── @eslint/[email protected]
├── @types/[email protected]
├── @types/[email protected]
├── @types/[email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
└── [email protected]

@Rov98
Copy link

Rov98 commented Jan 2, 2025

still having an issue and my user return null.
and another issue is what if u want to redirect to dynamic router after successfully login?

'use server'

import { auth, signIn } from "@/auth"
import { CredentialsSignin } from "next-auth";
import { redirect } from "next/navigation";

export async function handleSubmitForm(data: FormData) {
    try {
        await signIn("credentials", {
            redirect: false,
            email: data.get("email"),
            password: data.get("password"),
        });
    } catch (error: unknown) {
        if (error instanceof CredentialsSignin) {
            return {
                message: "wrong credentials",
                stack: error
            }
        }

        return {
            message: "wrong credentials"
        }
    }

    const user = await auth();
    console.log(user);
    redirect(`/${user?.user?.id}`);
}

@Rov98
Copy link

Rov98 commented Jan 2, 2025

still having an issue and my user return null. and another issue is what if u want to redirect to dynamic router after successfully login?

Found a solution. u need to handle it on different function and call this function when the handleSubmitForm is returning something, either it's client side or server side. it's also takes time for the jwt token to register in the cookies depends on your providers callback.


export async function handleSubmitForm(data: FormData) {
    try {
        await signIn("credentials", {
            redirect: false,
            email: data.get("email"),
            password: data.get("password"),
        });

        return {
            success: true,
            message: "login success"
        }
    } catch (error: unknown) {
        if (error instanceof CredentialsSignin) {
            return {
                success: false,
                message: "Email atau Password salah",
                stack: error
            }
        }

        return {
            success: false,
            message: "Email atau Password salah"
        }
    }
}

export async function handleRedirect() {
    const user = await auth();
    console.log(user);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.
Projects
None yet
Development

No branches or pull requests