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

CallbackRouteError on Credentials when following docs #11074

Open
theswampire opened this issue Jun 4, 2024 · 29 comments
Open

CallbackRouteError on Credentials when following docs #11074

theswampire opened this issue Jun 4, 2024 · 29 comments
Labels
bug Something isn't working providers triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.

Comments

@theswampire
Copy link

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 theswampire added bug Something isn't working providers triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. labels Jun 4, 2024
@shunkica
Copy link

shunkica commented Jun 7, 2024

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

@bibaswan7
Copy link

i am facing the exact same issue

@bibaswan7
Copy link

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'

@jessethomson
Copy link

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.

@nprdservice3
Copy link

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

@ondery
Copy link

ondery commented Jun 11, 2024

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.

@bibaswan7
Copy link

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

@danbeo95
Copy link

The same issue with 5.0.0-beta.19

@azizali

This comment has been minimized.

@ondery
Copy link

ondery commented Jun 13, 2024

@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.

@yukinoyuu
Copy link

yukinoyuu commented Jun 14, 2024

@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

@Ali-Raza764
Copy link

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="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/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)

@yukinoyuu
Copy link

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

@Ali-Raza764
Copy link

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

@riky-on-devcra

This comment has been minimized.

@sambecker
Copy link

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.

@Ali-Raza764
Copy link

Ali-Raza764 commented Jun 21, 2024

What if we can customize the authorize() function to return custom errors like this #9871
And here #9099

@hillaryodinson
Copy link

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

@ethanforvest
Copy link

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

@cherylli
Copy link

cherylli commented Jun 28, 2024

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",

@hillaryodinson
Copy link

hillaryodinson commented Jul 1, 2024

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

@SasquatchLV
Copy link

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?

@stephendewyer
Copy link

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: #10931.

@NitanJana
Copy link

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

this fixed it for me, thanks.

@cherylli
Copy link

cherylli commented Jul 7, 2024

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

@dynamiclynk
Copy link

dynamiclynk commented Jul 7, 2024

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.

@nkghis
Copy link

nkghis commented Jul 9, 2024

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

@mehrwarz
Copy link

mehrwarz commented Jul 9, 2024

"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

@Lucifer472
Copy link

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

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

No branches or pull requests