next-auth icon indicating copy to clipboard operation
next-auth copied to clipboard

CallbackRouteError on Credentials when following docs

Open theswampire opened this issue 1 year ago • 20 comments

Provider type

Credentials

Environment

System:
    OS: Linux 5.15 Ubuntu 22.04.3 LTS 22.04.3 LTS (Jammy Jellyfish)
    CPU: (16) x64 13th Gen Intel(R) Core(TM) i7-1360P
    Memory: 10.10 GB / 15.47 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 20.14.0 - ~/.nvm/versions/node/v20.14.0/bin/node
    npm: 10.7.0 - ~/.nvm/versions/node/v20.14.0/bin/npm
    pnpm: 9.1.4 - ~/.nvm/versions/node/v20.14.0/bin/pnpm
  npmPackages:
    next: 14.2.3 => 14.2.3 
    next-auth: 5.0.0-beta.19 => 5.0.0-beta.19 
    react: ^18 => 18.3.1 

Reproduction URL

https://github.com/theswampire/authjs-bug-reproduction

Describe the issue

When using the Credentials Provider, submitting a wrong password throws an CallbackRouteError. I tested it on 5.0.0-beta.18, 17 and 16 too but they throw CredentialsSignIn instead. I followed the v5 docs for setting up Nextjs and the Credentials Provider step by step thus this should be unexpected behaviour.

This is the auth.ts file:

// auth.ts
import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Credentials({
      credentials: {
        password: {},
      },

      authorize: async (credentials) => {
        if (credentials.password !== "password") {
          return null;
        }
        return { id: "admin", name: "admin" };
      },
    }),
  ],

  pages: {
    signIn: "/signin",
  },
});

The SignIn page looks like this:

import { signIn } from "@/auth";

export default function SignIn() {
  return (
    <form
      className="flex flex-col max-w-sm gap-2"
      action={async (formData) => {
        "use server";
        await signIn("credentials", formData);
      }}
    >
      <label>
        Password
        <input name="password" type="password" className="border" />
        <input name="redirectTo" type="hidden" value="/secure" />
      </label>
      <span>Password is &quot;password&quot;</span>
      <button className="bg-neutral-300">Sign In</button>
    </form>
  );
}

I tried to wrap the signIn call in a try-catch like in #11010 but that obviously didn't fix the underlying issue.

According to the documentation, the CallbackRouteError using the Credentials Provider means that either the authorize or another callback throws but I didn't overwrite any callbacks nor should authorize throw anything. Furthermore, if I remember correctly, when setting redirect to false, the SignInResponse contained ok: true and a Configuration error which I find odd.

How to reproduce

Enter anything but the correct password "password" and then the exception should be thrown. Using the correct password works just fine.

Expected behavior

Don't throw an exception.

theswampire avatar Jun 04 '24 17:06 theswampire

For me, just returning null from authorize() throws the CallbackRouteError

shunkica avatar Jun 07 '24 08:06 shunkica

i am facing the exact same issue

bibaswan7 avatar Jun 08 '24 17:06 bibaswan7

I am not sure if its done intentionally but I went through the next-auth code and figured out that, even though CredentialsSignin error is being thrown, it is then passed to CallbackRouteError, which is why we are getting CallbackRouteError. catch (e) { if (e instanceof AuthError) throw e; const error = new CallbackRouteError(e, { provider: provider.id }); logger.debug("callback route error details", { method, query, body }); throw error; }

here as you can see, catch recieves CredentialsSignin error. Since it is an instance of Error and not of AuthError, it skips the conditional, then rest is pretty much self explanatory.

In order to solve this issue, if its an CredentialsSignin error, then instead of checking error.type === 'CredentialsSignin', what you can do is error.cause.err.code === 'credentials'

bibaswan7 avatar Jun 09 '24 08:06 bibaswan7

Experiencing the same issue as others: If I return null from authorize, it throws this error.

This issue only started happening after upgrading to the latest beta. I went from 5.0.0-beta.4 to 5.0.0-beta.19 so I'm not sure which version in between those specifically causes this issue.

jessethomson avatar Jun 11 '24 05:06 jessethomson

I am not sure if its done intentionally but I went through the next-auth code and figured out that, even though CredentialsSignin error is being thrown, it is then passed to CallbackRouteError, which is why we are getting CallbackRouteError. catch (e) { if (e instanceof AuthError) throw e; const error = new CallbackRouteError(e, { provider: provider.id }); logger.debug("callback route error details", { method, query, body }); throw error; }

here as you can see, catch recieves CredentialsSignin error. Since it is an instance of Error and not of AuthError, it skips the conditional, then rest is pretty much self explanatory.

In order to solve this issue, if its an CredentialsSignin error, then instead of checking error.type === 'CredentialsSignin', what you can do is error.cause.err.code === 'credentials'

thanks it worked

nprdservice3 avatar Jun 11 '24 07:06 nprdservice3

I am not sure if its done intentionally but I went through the next-auth code and figured out that, even though CredentialsSignin error is being thrown, it is then passed to CallbackRouteError, which is why we are getting CallbackRouteError. catch (e) { if (e instanceof AuthError) throw e; const error = new CallbackRouteError(e, { provider: provider.id }); logger.debug("callback route error details", { method, query, body }); throw error; }

here as you can see, catch recieves CredentialsSignin error. Since it is an instance of Error and not of AuthError, it skips the conditional, then rest is pretty much self explanatory.

In order to solve this issue, if its an CredentialsSignin error, then instead of checking error.type === 'CredentialsSignin', what you can do is error.cause.err.code === 'credentials'

When done this way, it will give the same error again if there is a connection problem with the database.

ondery avatar Jun 11 '24 09:06 ondery

@ondery what error are you getting? mind sharing the code snippets?

bibaswan7 avatar Jun 11 '24 13:06 bibaswan7

The same issue with 5.0.0-beta.19

danbeo95 avatar Jun 12 '24 03:06 danbeo95

same issue for me.

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

azizali avatar Jun 13 '24 02:06 azizali

@ondery what error are you getting? mind sharing the code snippets?

@bibaswan7 error.cause.err.code === 'credentials' will give true in case of database connection problems too unfortunately.

ondery avatar Jun 13 '24 04:06 ondery

@bibaswan7 I came to the same conclusion, but since I'm using typescript, it screams at me saying that property of code does not exist on type Error. Does anyone know a way to do typing properly with this weird wrapping, as well as @ondery mentioned, a way to disambiguate this error further?

I looked into the typing issue a bit more and found commit d089923 , where the typing of CredentialsSignin is changed from extending SignInError to extending Error. This further ambiguities the typing on the error. I found the issue for it in #11155

Kupinaaa avatar Jun 14 '24 16:06 Kupinaaa

I think I have a fix of just signing in and handling an error if the password is incorrect. Instead of using signIn() function from the @/auth.js file we can do somthing like this in the signinForm:

import { signIn } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
import Providers from "../components/Providers";

const SignInForm = () => {
  const [loading, setLoading] = useState(false);
  const router = useRouter();
  const [error, setError] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();
    const email = e.target.email.value;
    const password = e.target.password.value;

    try {
      setLoading(true);
      setError("");
      const res = await signIn("credentials", {
        redirect: false,
        email,
        password,
      });

      //* The res here has no credentials data only error:true or error:null so we can manage the state based on that
      //* Next auth does not send the data due to security reasons
      if (res.error === null) {
        router.push("/protected");
      }
      if (res.error) {
        setError("Check Your Email Or Password");
      }
    } catch (error) {
      console.error(error.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="shadow bg-gray-400 p-4 rounded-md m-2 min-w-[20rem]">
      <form onSubmit={handleSubmit} className="flex flex-col gap-4">
        <input
          type="text"
          name="email"
          placeholder="Email"
          className="border-b-2 border-gray-400 focus:border-red-500 outline-none  p-2 transition disabled:border-none disabled:bg-gray-400"
          required
          disabled={loading}
        />
        <input
          type="password"
          name="password"
          placeholder="Password"
          className="border-b-2 border-gray-400 focus:border-red-500 outline-none  p-2 transition disabled:border-none disabled:bg-gray-400"
          required
          disabled={loading}
        />
        <p className="text-red-500">{error}</p>
        <button
          type="submit"
          className="p-2 px-4 bg-gray-800 rounded-md text-white disabled:border-none disabled:bg-gray-600"
          disabled={loading}
        >
          Submit
        </button>
      </form>
      <div className="my-2 w-full text-center"> Or</div>
      <div className="w-full">
        <Providers />
      </div>
      <Link href="/auth/signup">SignUp</Link>
    </div>
  );
};

export default SignInForm;

If the signIn() functions throws an error Then it means that The credentials are invalid... And If you are storing your user credentials in a database you can handle that authentication from the auth.js file using a server action:

import GitHub from "next-auth/providers/github";
import Google from "next-auth/providers/google";
import CredentialsProvider from "next-auth/providers/credentials";
import {
  SignInWithEmailAndPassword,
  oauthSignIn,
} from "@/actions/user/user.actions";

const providers = [
  Google,
  GitHub,
  CredentialsProvider({
    name: "Credentials",
    credentials: {
      email: { label: "Email", type: "email" },
      password: { label: "Password", type: "password" },
    },
    async authorize(credentials) {
      const user = await SignInWithEmailAndPassword(credentials); // User verification logic
      if (user.status === 400) {
        return null;
      }
      return user.data;
    },
  }),
];

export const providerMap = providers.map((provider) => {
  if (typeof provider === "function") {
    const providerData = provider();
    return { id: providerData.id, name: providerData.name };
  } else {
    return { id: provider.id, name: provider.name };
  }
});

export const { handlers, auth, signIn, signOut, unstable_update } = NextAuth({
  providers,
  pages: {
    signIn: "/auth/sign-in",
  },
  callbacks: {
    async signIn({ user, account }) {
      if (account.provider === "google" || account.provider === "github") {
        const { name, email, image } = user;
        const payload = {
          name,
          email,
          avatar: image,
          authType: "Oauth",
        };

        const res = await oauthSignIn(payload);
        user.id = res.data._id.toString();
        user.isVerified = res.data.isVerified;
        user.image = res.data.avatar;

        return user;
      }
      // Default to allow sign-in
      return user;
    },
    async jwt({ trigger, token, user }) {
      // Add user information to the token during sign-in
      if (trigger === "update") {
        token.isVerified = session.user.isVerified;
      }
      if (user) {
        console.log(user);
        const id = user._id?.toString() || user.id;
        token.id = id;
        token.email = user.email;
        token.name = user.name;
        token.isVerified = user.isVerified;
        token.picture = user.avatar || user.image;
      }
      return token;
    },
    async session({ session, token }) {
      session.user.id = token.id;
      session.user.email = token.email;
      session.user.name = token.name;
      session.user.image = token.picture;
      session.user.isVerified = token.isVerified;
      return session;
    },
  },
});```
I think this pretty much works for now. If you want to see detailed implementation for this you can checkout this [repo](https://github.com/Ali-Raza764/next-auth-tookit)

Ali-Raza764 avatar Jun 15 '24 04:06 Ali-Raza764

Yeah @Ali-Raza764 That's exactly what I do currently, but I just want a bit more fine grain control over the error, and it is supposed to work, but just doesn't. For example, what if my API endpoint became unreachable and just timed out.. It would still say to check the credentials on the client side, even if they are correct. That would lead the user to try a bunch of times and then try to reset the password, which is not something I would want. Instead I want to distinguish whether it's because of the credentials (user does not exist on the token in the Callback). I think this could just be fixed with the typing, as @bibaswan7 said.

The issue for the broken types is #11155.

The types are broken in #11050

Screenshot 2024-06-14 at 11 31 09 AM

Kupinaaa avatar Jun 15 '24 14:06 Kupinaaa

Ok I do understand that. So we can wait until the new release makes a fix ?

Ali-Raza764 avatar Jun 16 '24 03:06 Ali-Raza764

any updates?

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

riky-on-devcra avatar Jun 19 '24 02:06 riky-on-devcra

In case it's helpful, can confirm that Credentials-based error handling broke for me when switching to from beta.18 to beta.19.

Errors previously classified CredentialsSignin became classified CallbackRouteError.

sambecker avatar Jun 21 '24 00:06 sambecker

What if we can customize the authorize() function to return custom errors like this https://github.com/nextauthjs/next-auth/pull/9871 And here https://github.com/nextauthjs/next-auth/issues/9099

Ali-Raza764 avatar Jun 21 '24 06:06 Ali-Raza764

I rolled back from 5.0.0-beta.19 to 5.0.0-beta.18 and it worked for me. Clearly its a bug on 5.0.0-beta.19 so until a new update comes I ll stick to the previous version

hillaryodinson avatar Jun 23 '24 01:06 hillaryodinson

I rolled back from 5.0.0-beta.19 to 5.0.0-beta.18 and it worked for me. Clearly its a bug on 5.0.0-beta.19 so until a new update comes I ll stick to the previous version

Yeah, downgrading fixed the issue for me too

ethanforvest avatar Jun 24 '24 20:06 ethanforvest

I tested it on 5.0.0-beta.18, 17 and 16 too but they throw CredentialsSignIn instead.

Exactly same issue as OP, downgrading to beta.18 didn't work

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

cherylli avatar Jun 28 '24 14:06 cherylli

use npm install [email protected] or yarn add [email protected] that should help. make sure your provider authorize returns null or user data

edit: I forgot to add yarn for yarn users. my bad

hillaryodinson avatar Jul 01 '24 02:07 hillaryodinson

use npm install [email protected] that should help. make sure your provider authorize returns null or user data

So which version of the code is working for you now?? Can you show an example how you got the custom errors?

SasquatchLV avatar Jul 01 '24 07:07 SasquatchLV

I am facing a similar issue with @auth/core:0.31.0 and @auth/sveltekit:1.1.0. The response object for signIn is always returning no error when user submits invalid credentials. The issue in more detail: https://github.com/nextauthjs/next-auth/issues/10931.

stephendewyer avatar Jul 01 '24 15:07 stephendewyer

use npm install [email protected] that should help. make sure your provider authorize returns null or user data

this fixed it for me, thanks.

NitanJana avatar Jul 03 '24 05:07 NitanJana

I tested it on 5.0.0-beta.18, 17 and 16 too but they throw CredentialsSignIn instead.

Exactly same issue as OP, downgrading to beta.18 didn't work

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

Tried again a week later with the same code and yarn add [email protected] worked for me.

Also don't throw any other errors except CredentialSignin error, it gives configuration error

so either null or CredentialSignin error will work for [email protected] but they are both broken in beta.19

cherylli avatar Jul 07 '24 04:07 cherylli

downgrading from "next-auth": "^5.0.0-beta.19" to 18 allows a custom error responses to throw and be handled in the client. Can't understand abstracting this away from the developer 😕, at least default it to off and allow us to specify an process env var to enable throwing custom core authorize errors and any auth life cycle hook.

dynamiclynk avatar Jul 07 '24 12:07 dynamiclynk

use npm install [email protected] or yarn add [email protected] that should help. make sure your provider authorize returns null or user data

edit: I forgot to add yarn for yarn users. my bad

thanks that is work well now

nkghis avatar Jul 09 '24 12:07 nkghis

"next": "14.2.4",
"next-auth": "^5.0.0-beta.19",
I was able to throw my custom error in server side and send message to client. here it is: https://stackoverflow.com/questions/78627862/next-auth-signin-return-errorconfiguration-in-responce-for-invalid-credenti/78723173#78723173

mehrwarz avatar Jul 09 '24 23:07 mehrwarz

just downgrade to "next-auth": "^5.0.0-beta.18" it will fix the issue i was having the same problem.

To fix it first remove next-auth from package json then run npm i then install npm install [email protected] and make sure to return null with authorize function or if you want use custom error msg like this

import { CredentialsSignin } from "next-auth";

class WrongPassword extends CredentialsSignin {
  code = "Wrong_Password";
}

const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) throw new WrongPassword();

inside of the login function

 try {
    await signIn("credentials", {
      redirect: true,
      redirectTo: defaultRedirect,
      email,
      password,
    });
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case "CredentialsSignin":
          return "Invalid credentials";
        case "CallbackRouteError":
          return "Something went Wrong!";
        default:
          return "Something went wrong";
      }
    }
    throw error;
  }

i hope this helps

Lucifer472 avatar Jul 10 '24 05:07 Lucifer472

Was also seeing this issue and thought it was me! Downgrading to 5.0.0-beta.18 resolved the issue for me!

Thanks.

liamicy3aaa avatar Jul 17 '24 21:07 liamicy3aaa