2

I am using the NextAuth.js credentials provider for the log in procedure. When logging in I am catching the errors in a try-catch block and set the error state accordingly, but I do not display the error state anywhere yet. Even though I am catching the errors, a 401 unauthorized gets thrown when trying to log in. I am using wrong credentials, thus expecting an error called CredentialsSignin which I am getting, but additionally I am getting the 401 every time. The problem is that I am not able to detect where it is thrown, that might be the reason I am not able to handle it.

401 thrown in console

Here the code of my custom log in page:

import { InferGetServerSidePropsType } from "next"
import { CtxOrReq } from "next-auth/client/_utils";
import { getCsrfToken, getSession, signIn } from "next-auth/react"
import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react";
import { useFormik } from "formik"
import * as Yup from "yup"

export default function Login({ csrfToken }: InferGetServerSidePropsType<typeof getServerSideProps>) {

    const [loading, setLoading] = useState(false)
    const [error, setError] = useState('')
    const router = useRouter()

    type Credentials = {
        email: string
        password: string
    }

    let credentials: Credentials;

    const handleLogin = useCallback(async (credentials) => {
        if (!credentials.email) {
            return setError('email is missing')
        }
        if (!credentials.password) {
            return setError('password is missing')
        }
        try {
            setLoading(true)
            const response: any = await signIn('credentials', { ...credentials, redirect: false }) // find right type
            if (response.error && response.error === 'CredentialsSignin') {
                setError('email or password are wrong')
            } else {
                setError('')
                router.push('/')
            }
        } catch {
            setError('login failed')
        } finally {
            setLoading(false)
        }
    }, [router])

    const formik = useFormik({
        initialValues: {
            email: "",
            password: ""
        },
        validationSchema: Yup.object({
            email: Yup.string()
                .required("email address is required"),
            password: Yup.string()
                .required("password is required")
        }),
        onSubmit: values => {
            credentials = {
                email: values.email,
                password: values.password
            }
            handleLogin(credentials)
        }
    })

    return (
        <form onSubmit={formik.handleSubmit} noValidate>
            <input name="csrfToken" type="hidden" defaultValue={csrfToken} />
            <label>
                Email
                <input
                    name="email"
                    type="email"
                    value={formik.values.email}
                    onChange={formik.handleChange}
                    onBlur={formik.handleBlur} />
            </label>
            {formik.touched.email && formik.errors.email && <p>{formik.errors.email}</p>}
            <label>
                Password
                <input
                    name="password"
                    type="password"
                    value={formik.values.password}
                    onChange={formik.handleChange}
                    onBlur={formik.handleBlur} />
            </label>
            {formik.touched.password && formik.errors.password && <p>{formik.errors.password}</p>}
            <button type="submit">Login</button>
        </form>
    )
}

export async function getServerSideProps(context: CtxOrReq | undefined) {

    const session = await getSession(context)

    if (session) {
        return {
            redirect: { destination: '/' }
        }
    }

    return {
        props: {
            csrfToken: await getCsrfToken(context)
        },
    }
}

Here the code of my [...nextauth].ts API page:

import NextAuth from 'next-auth'
import { PrismaAdapter } from '@next-auth/prisma-adapter'
import { prisma } from '../../../prisma/prisma_client'
import CredentialsProvider from "next-auth/providers/credentials"
import { compare } from 'bcryptjs'

export default NextAuth({

  adapter: PrismaAdapter(prisma),

  providers: [
    CredentialsProvider({
      name: "Credentials",
      credentials: {
        email: {},
        password: {}
      },
      async authorize(credentials) {
        if (!credentials) {
          return null
        }

        const { email } = credentials
        const { password } = credentials

        const storedUser = await prisma.user.findUnique({
          where: {
            email
          }, select: {
            id: true,
            email: true,
            hashedPassword: true,
            company: {
              select: {
                id: true,
                name: true
              }
            }
          }
        })

        if (!storedUser) {
          return null
        }

        const user = {
          id: storedUser?.id,
          email,
          comanyId: storedUser?.company?.id,
          companyName: storedUser?.company?.name,
        }

        const validatePassword = await compare(password, storedUser.hashedPassword)
        return validatePassword ? user : null
      }
    })
  ],

  pages: {
    signIn: '/login'
  },

  callbacks: {
    async jwt({ token, user }) {
      user && (token.user = user)
      return token
    },
    async session({ session, token }) {
      session.user = token.user
      return session
    }
  },

  session: {
    strategy: "jwt",
    maxAge: 30 * 24 * 60 * 60 // 30 days
  }
})
  • Isn't the 401 error the response you get from the API that then triggers the `CredentialsSignin` error on the client code? That would be the expected behaviour. – juliomalves May 11 '22 at 22:38
  • This is what I thought too, but why am I not able to catch it then? and why is it not mentioned in the documentation? – Jakob Basshunter Widmann May 12 '22 at 13:40
  • The error isn't thrown, you know it errored by checking the `response.error` field. See https://next-auth.js.org/getting-started/client#using-the-redirect-false-option. – juliomalves May 12 '22 at 13:56
  • @juliomalves I added a picture to my question that shows the thrown error in the console and I am clearly not logging it. – Jakob Basshunter Widmann May 17 '22 at 08:10
  • did you find any solution for this? – Hasan Haghniya Jun 10 '22 at 22:48
  • No and this really makes me sad. I really looked up the whole internet to find a solution or explanation to this. I would highly appreciate any help or suggestions. I still don't know if the 401 is the standard answer to a failed login from the signin-function and even if it is, why I am unable to catch it. – Jakob Basshunter Widmann Jun 14 '22 at 19:00
  • Same here, some API routes in NextJS just randomly give 401, even on a GET that I browse or redirect to within the browser… – chitzui Jun 29 '23 at 09:11

2 Answers2

0

Please make sure, that you set 'withCredentials' to 'true' for your API object

Marcin-99
  • 41
  • 2
0

Encountered the same issue and manage to resolve it. The first block of code below is the buggy version and the second block is the working version.

So, there's a few things to note:

  • I returned the same data for catch and response because I wanted to determine if the issue was cause by the data which was returned. But it wasn't the case, so this was reverted in the working version.
  • In the working version, there's return for the promise and a return within the promise. My understanding is that the response and catch blocks are functions by itself, and returning within the inner block will not propagate the value such that it is returned by the authorize function. In other words, I'm returning nothing - resulting in 401.
  • With the same understanding, the issue with OP's code is it is always failing to return a user object. Without running the code, I can't pinpoint it for sure but there are a few possible paths which leads to so.
authorize: async (credentials) => {
axios.post('http://localhost:8000/auth/signin/', {
    email: credentials.email,
    id: credentials.id
}).then((response) => {
    const user = { id: 1, token: "1234", name: 'John', email: 'john@abc.com' }
    return user
}).catch((e) => {
    const user = { id: 1, token: "1234", name: 'John', email: 'john@abc.com' }
    return user
});
};
authorize: async (credentials) => {
const request = axios.post('http://localhost:8000/auth/signin/', {
    email: credentials.email,
    id: credentials.id
})

return await request.then((response) => {
    const user = { id: 1, token: "1234", name: 'John', email: 'john@abc.com' }
    return user
}).catch((e) => {
    console.log(e);
    return null;
});
};