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.
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
}
})
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:
catch
andresponse
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.return
for the promise and areturn
within the promise. My understanding is that theresponse
andcatch
blocks are functions by itself, and returning within the inner block will not propagate the value such that it is returned by theauthorize
function. In other words, I'm returning nothing - resulting in401
.