skip to Main Content

I am using next-auth.js version 4.19.2 with the "credentials" (db) provider and some custom sign-in, and signout pages. I seem unable to return what it expects from the authorize() handler. I would like to return the authenticated user or an error message. I tried ‘return user’ and ‘return null’ as well as resolving and rejecting a Promise ‘return Promise.resolve(user)’ and ‘return Promise.reject(null)’… neither worked. Can someone spot the issue below? Thank you!

import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import GoogleProvider from "next-auth/providers/google";
import User from "../../../../models/User";

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
    CredentialsProvider({
      name: "Credentials",

      async authorize(credentials, req) {
        const { username, password } = credentials;

        const user = await User.findOne({ email: username }).exec();

        if (user) {
          console.log("user", user);
          await user.comparePassword(password, function (err, isMatch) {
            console.log("comparePassword err, isMatch", err, isMatch);
            if (err) throw err;
            if (isMatch) {
              console.log("IS MATCH!");
              return Promise.resolve(user);
            }
          });
        } else {
          return Promise.reject(null);
        }
      },
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
  pages: {
    signIn: "/auth/signin",
    signOut: "/auth/signout",
    error: "/auth/error", // Error code passed in query string as ?error=
    verifyRequest: "/auth/verify-request", // (used for check email message)
    newUser: "/auth/new-user", // New users will be directed here on first sign in (leave the property out if not of interest)
  },
};

export default NextAuth(authOptions);

Using it like this:

<button
  type="submit"
  className="btn btn-primary w-100 mt-4"
  onClick={() =>
    signIn("credentials", {
      redirect: false,
      username: state.username,
      password: state.password,
      callbackUrl: "/",
    }).then(({ ok, error }) => {
      if (ok) {
         alert("OK");
      } else {
         console.log(error);
         alert("NOT OK");
      }
    })
    }
   >
   Sign in
 </button>

What am I doing wrong here?

4

Answers


  1. Chosen as BEST ANSWER

    The issue was with the signin had a form tag that was getting submitted at the same time as the sign in ajax call.


  2. You have to return an object not a promise :

    let returnedValue;
    
    if (user) {
      await user.comparePassword(password, function (err, isMatch) {
        if (err) returnedValue = null; // throwing an error here will prevent the function from returning
        if (isMatch) returnedValue = user;
      });
    } else {
      returnedValue = null;
    }
    
    return returnedValue;
    

    Also here you are only returning the user you are not storing it in your session to do that you should use callbacks :

    callbacks: {
     async jwt({ user, token }) {
       if (user) {
         token.user = user;
       }
       return token;
     },
     async session({ session, token }) {
       session.user = token.user;
       return session;
     },
    },
    
    Login or Signup to reply.
  3. This my current next-auth with credentials and it working fine

    import NextAuth from 'next-auth'
    import GoogleProvider from "next-auth/providers/google";
    import CredentialsProvider from "next-auth/providers/credentials"
    
    import dbConnect from '../../../lib/dbConnect';
    import User from '../../../models/User';
    import { compare, hash } from 'bcryptjs';
    import crypto from 'crypto';
    
    import sendVerificationEmail from '../../../middleware/emailService';
    
    
    let AuthUser;
    export const authOptions = {
      providers: [
        // OAuth authentication providers...
        GoogleProvider({
          clientId: process.env.GOOGLE_ID,
          clientSecret: process.env.GOOGLE_SECRET,
          authorization: {
            params: {
              prompt: "consent",
              access_type: "offline",
              response_type: "code"
            }
          }
        }),
        CredentialsProvider({
          name: 'Credentials',
          async authorize(credentials, req) {
            await dbConnect()
            
            //check user existance
            const result = await User.findOne({email: credentials.email}).select('+password')
            if(!result){
              throw new Error('No user Found With Email Please Sign Up!')
            }
    
            if(result.verified){
              //compare password
              const checkPassword = await compare(credentials.password, result.password)
              if(!checkPassword || result.email !== credentials.email){
                throw new Error("Email or Password dosen't match")
              }
              AuthUser = result
              return result
            }else{
              sendVerificationEmail(result.email, result.verificationToken)
              throw new Error("Please Confirm Your Email!")
            }
    
    
          }
        })
      ],
      callbacks:{
        signIn: async ({ user, account, profile }) => {
          await dbConnect()
    
          if (account.provider === "google") {
            const existingUser = await User.findOne({ email: user.email });
    
            if (existingUser) {
              AuthUser = existingUser
              return existingUser;
            }
            
            const randomPassword = crypto.randomBytes(8).toString('hex');
    
            const hashedPassword = await hash(randomPassword, 12);
    
            const newUser = await User.create({
              name: user.name,
              email:user.email,
              password:hashedPassword,
              provider:true,
              providerName:"google"
            });
    
            AuthUser = newUser
            return newUser;
          }else{
            return true
          }
        },
        session: async ({session}) => {
    
          if(AuthUser){
            // Add custom data to the session object
            session.userData = {
              isAdmin: AuthUser.isAdmin,
              id: AuthUser._id,
            };
          }
          return session;
        },
      },
      secret: process.env.NEXT_AUTH_SECRET,
      database: process.env.DB_URL  
    }
    
    export default NextAuth(authOptions)
    
    Login or Signup to reply.
  4. Since you use this await user.comparePassword, you must define comparePassword on userSchema. It should be like this

    userSchema.methods.comparePassword = async function (enteredPassword) {
      // jose or bcrypt is used
      return await bcrypt.compare(enteredPassword, this.password);
    };
    

    if you set this correctly then in authorize()

     if (!user) {
          throw new Error("Invalid credentials");
      }
     const isPasswordMatched = await user.comparePassword(password);
    
     if (!isPasswordMatched) {
          throw new Error("error message");
     }
     
    return Promise.resolve(user);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search