• Home
  • Get Started
  • Updates
  • Support
  • Shop
  • Pricing
  • AI News
Get Started
  • Login
  • Register
Neuraldemy
Cart / 0.00$

No products in the cart.

No Result
View All Result
Get Started
Neuraldemy
Get started
Home Web Development

[Fixed]CallbackRouteError & CredentialsSignin Error In Next-Auth@Beta(Auth.Js)

Amritesh Kumar by Amritesh Kumar
April 20, 2025
in Web Development
Reading Time: 14 mins read
A A

When I first started using NextAuth.js beta (next-auth@beta) with the Credentials provider, I figured it would be a quick setup: collect the user’s email and password, hand them off to signIn, and let NextAuth handle the rest. Instead, I spent four hours chasing down a confusing CallbackRouteError: Illegal arguments: string, undefined, and sometimes CredentialsSignin Erorr.

I was frustrated because their documentation does not look good to me. I have shared my response in the GitHub issue here but I thought why not share it for other people to find. If you’re seeing the same error, this post will walk you through everything I learned: the root causes, the code you need, and a robust pattern for validation and error handling that you can help you work with Auth.js smoothly.

Table of Contents

  • 1. What To Do If You See This “Illegal arguments: string, undefined” Happens
  • 2. Validate Early with zod
  • 3. Implementing authorize() Correctly
  • 4. Handling signIn and Redirects in a Server Action

1. What To Do If You See This “Illegal arguments: string, undefined” Happens

If you seeing this along with CallbackRouteError. Please check your function arguments and see if you are passing the arguments correctly. If you are using bcrypt, then make sure the database table column names are correct.


2. Validate Early with zod

Before you ever hit your authentication endpoint, validate user input so you never send bad data to signIn. I use zod on the server action (or you can use it in React before submitting):

import { z } from "zod";

const loginSchema = z.object({
  email: z.string().email("Enter a valid email"),
  password: z
    .string()
    .min(8, "Password must be at least 8 characters")
    .regex(/[A-Z]/, "Include at least one uppercase letter")
    .regex(/[a-z]/, "Include at least one lowercase letter")
    .regex(/\d/, "Include at least one number"),
});

Code language: TypeScript (typescript)

In your server action:

export async function loginUser(formData: FormData) {
  const email = formData.get("email")?.toString() ?? "";
  const password = formData.get("password")?.toString() ?? "";

  try {
    loginSchema.parse({ email, password });
  } catch (e) {
    const err = e as z.ZodError;
    return { error: err.errors[0].message, success: null };
  }

  // … proceed to signIn …
}

Code language: TypeScript (typescript)

Now malformed emails or weak passwords get caught immediately, and your authorize() callback never sees undefined fields. So, don’t forget to deal with the formData or inputs to minimize the chances of getting the error by Auth.js.

3. Implementing authorize() Correctly

Once input passes your zod schema, you’ll call the credentials provider’s authorize function in auth.ts (or auth.js). In NextAuth.js beta, the authorize callback must behave in a specific way: return null for invalid credentials or a user object on success. Don’t throw an error inside authorize, because in this case such thrown errors always manifest as a CallbackRouteError on your callback route. Instead, check first that both email and password exist. If either is missing, immediately return null. It may not be needed if you are already checking using zod. Next, fetch the user record from your database—whether it’s Supabase, Prisma, or another ORM—and verify that a hashed password field actually arrives in your query response.

import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { supabase } from "./lib/supabaseClient";
import bcrypt from "bcryptjs";

export default NextAuth({
  providers: [
    Credentials({
      name: "Credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" },
      },
      authorize: async (credentials) => {
        // 1. Ensure credentials exist
        if (!credentials?.email || !credentials?.password) {
          return null;
        }

        // 2. Fetch user, including your exact hashed-password column
        const { data: user, error } = await supabase
          .from("users")
          .select("id, email, password, username")
          .eq("email", credentials.email)
          .single();

        if (error || !user) {
          // user not found
          return null;
        }

        // 3. Compare passwords (both must be strings)
        const isValid = await bcrypt.compare(
          credentials.password,
          user.password
        );
        if (!isValid) {
          // wrong password
          return null;
        }

        // 4. Return minimal user object for session callback
        return {
          id: user.id,
          email: user.email,
          name: user.username,
        };
      },
    }),
  ],

  session: { strategy: "jwt" },
  pages: { signIn: "/login" },  // here you can have your own page, 

  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id;
      }
      return token;
    },
    async session({ session, token }) {
      if (session.user) {
        session.user.id = token.id as string;
      }
      return session;
    },
  },
});

Code language: TypeScript (typescript)

4. Handling signIn and Redirects in a Server Action

On the client side (or in your server action), wrap your signIn("credentials", …) call in a try/catch block. NextAuth’s signIn can reject in two common ways: a “CredentialsSignin” error when credentials don’t match, or a CallbackRouteError when you accidentally threw inside authorize or if something happens unexpected. In your catch block, detect if the caught error is an instance of Auth.js’s AuthError (imported from "next-auth"), and check its type. For both "CredentialsSignin" and "CallbackRouteError", return a user‑friendly message like “Invalid email or password. Please try again.” For any other AuthError types, show a generic “Authentication error occurred.”

A final subtlety comes when you want to redirect on successful login. Next.js server actions use a special NEXT_REDIRECT error under the hood to signal a redirect. If you catch all errors blindly, you’ll also catch this redirect signal, preventing it from working. To handle this, inspect the caught error’s message for the substring "NEXT_REDIRECT". If you see it, re‑throw the error so that Next.js can perform the intended redirect. Alternatively, you could handle redirects entirely on the frontend with useRouter, but re‑throwing in your server action keeps your login logic self‑contained. check the code below.

Now let say you have login page. The idea is to deal with things like incorrect email format or password using zod before you send details to signIn method.

// Rest of the code above ......

const [state, formAction, isPending] = useActionState(loginUser, {
    error: null,
    success: null,
  });

<form id="login" action={formAction}>

// Rest of the code below ......
Code language: TypeScript (typescript)
export async function loginUser(
  prevState: { error: string | null; success: string | null },
  formData: FormData
): Promise<{ error: string | null; success: string | null }> {
  const email = formData.get("email")?.toString();
  const password = formData.get("password")?.toString();

// Here you can validate the formData using zod and sendi errors......

// zod validation logic......

// ......

try {

  // Once you have verified the expected schema of your formData use signIn method. 

    await signIn("credentials", {
      email,
      password,
      redirectTo: "/dashboard",
    });

    return {
      error: null,
      success: "Login successful! Redirecting...",
    };
  } catch (error) {
   

   // Here I am checking the error type to pass the signal to client.

    if (error instanceof AuthError) {
      switch (error.type) {
        case "CredentialsSignin":
        case "CallbackRouteError":
          return {
            error: "Invalid email or password. Try again!",
            success: null,
          };
        default:
          return { error: "An authentication error occurred.", success: null };
      }
    }
  
   // Here I need to rethrow the "Next_DIRECT" error so that redirection can work inside the server action, otherwise catch block will prevent it, and you will see another error. You can use useRouter if not this.

    if (
      error &&
      typeof error === "object" &&
      "message" in error &&
      typeof error.message === "string" &&
      error.message.includes("NEXT_REDIRECT")
    ) {
      throw error;
    }
   
 // For something unexpected
    return {
      error: "An unexpected error occurred. Please try again.",
      success: null,
    };
  }
}
Code language: TypeScript (typescript)

Here’s the high‑level flow:

  1. Validate form input with zod before calling signIn.
  2. In authorize(), return null for any missing or invalid data; never throw.
  3. Ensure your database query includes the precise hashed password column.
  4. Compare passwords with bcrypt.compare, returning null if it fails.
  5. Return a clean user object on success.
  6. Wrap signIn in try/catch, handle AuthError types for bad credentials, re‑throw NEXT_REDIRECT, and catch any other unexpected errors gracefully.

This pattern prevents the dreaded CallbackRouteError and gives your users clear, friendly feedback as schema validation happens in one place, authorization logic in another, and error handling in a concise try/catch. Although the current beta of Auth.js makes this a bit more complex, but following these steps will save you hours of debugging.

In the future, I hope the NextAuth team provides a built‑in mechanism for returning custom error messages from authorize() without resorting to throwing or returning null. Meanwhile, this approach works consistently with the beta, at least for me. If you have any other solution, let me know.

Tags: Javascript
Previous Post

What’s Is AI Labyrinth By Cloudflare?

Amritesh Kumar

Amritesh Kumar

I believe you are not dumb or unintelligent; you just never had someone who could simplify the concepts you struggled to understand. My goal here is to simplify AI for all. Please help me improve this platform by contributing your knowledge on machine learning and data science, or help me improve current tutorials. I want to keep all the resources free except for support and certifications. Email me @amriteshkr18@gmail.com.

Related Posts

CSS Grid – The Ultimate Guide

Understanding Memoization in JavaScript: Optimizing Function Calls with Caching

  • Customer Support
  • Get Started
  • Ask Your ML Queries
  • Contact
  • Privacy Policy
  • Terms Of Use
Neuraldemy

© 2024 - A learning platform by Odist Magazine

Welcome Back!

Login to your account below

Forgotten Password? Sign Up

Create New Account!

Fill the forms below to register

*By registering into our website, you agree to the Terms & Conditions and Privacy Policy.
All fields are required. Log In

Retrieve your password

Please enter your username or email address to reset your password.

Log In
No Result
View All Result
  • Home
  • Get Started
  • Updates
  • Support
  • Shop
  • Pricing
  • AI News
  • Login
  • Sign Up
  • Cart
Order Details

© 2024 - A learning platform by Odist Magazine

This website uses cookies. By continuing to use this website you are giving consent to cookies being used.
Are you sure want to unlock this post?
Unlock left : 0
Are you sure want to cancel subscription?
0