I am trying to add authentication to my nextjs app using Next-auth (using the credentials provider) and I can't find a way to customize the error (i.e wrong password, email doesn't exist ...)
here is my auth.ts code :
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials";
import { PrismaAdapter } from "@auth/prisma-adapter"
import { db } from "./app/lib/db";
import bcryptjs from 'bcryptjs'
export class IncorrectPasswordError extends Error {
constructor(message:string) {
super(message);
this.name = 'IncorrectPasswordError';
}
}
interface User {
id : string,
name : string,
username : string,
email : string
}
export const { handlers, signIn, signOut, auth } = NextAuth({
secret : process.env.NEXTAUTH_SECRET,
pages : {
signIn: '/sign-in',
},
adapter: PrismaAdapter(db),
session : {
strategy: 'jwt'
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id
token.username = user?.username
}
return token
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string
session.user.username = token.username as string
}
return session
}
},
providers: [
Credentials({
// You can specify which fields should be submitted, by adding keys to the credentials
object.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
email: {label:"Email",type:"email",placeholder:"jb@gmail.com"},
password: {label:"password",type:'password'},
},
authorize: async (credentials) => {
if(!credentials?.email || !credentials?.password){
console.log('Missing email or password');
return null;
}
// try{
console.log('Attempting to authenticate user:', credentials.email);
const existingUser = await db.user.findUnique({
where:{email: credentials?.email}
});
if(!existingUser){
console.log('user do not exist')
throw new Error('user does not exist');
return null;
}
const pwdMatch = await bcryptjs.compare(credentials?.password, existingUser?.password)
if(!pwdMatch){
console.log('pwd does not match')
return null;
}
const {id, email, username} = existingUser;
console.log('Authentication successful for user:', email);
return {
id,email,username
}
// }catch (error) {
// // Handle errors gracefully (e.g., logging, error messages)
// console.error('Error during authentication:', error);
// throw new Error('Authentication failed'); // Or customize error message
// }
},
}),
],
})
and here is my sign-in page :
'use client'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import * as z from 'zod'
import axios from 'axios'
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { AlertCircle, CheckCircle2 } from 'lucide-react'
import { useRouter } from 'next/navigation'
import { SignIn } from '@/app/lib/auth-action'
import Link from 'next/link'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { toast } from 'react-toastify'
import { IncorrectPasswordError } from '@/auth'
const signUpSchema = z.object({
email: z.string().min(8, 'Email is required').email('Invalid email address'),
password: z.string().min(1, 'Password is required'),
})
type SignUpFormData = z.infer<typeof signUpSchema>
export default function SignUpPage() {
const [isLoading, setIsLoading] = useState(false)
const [apiResponse, setApiResponse] = useState<{ success: boolean; message: string } | null>(null)
const router = useRouter()
const form = useForm<SignUpFormData>({
resolver: zodResolver(signUpSchema),
defaultValues : {
email : "",
password : ""
}
});
const {isSubmitting ,isValid} = form.formState
const onSubmit = async (data: SignUpFormData) => {
try {
const response = await SignIn(data);
console.log('SignIn response:', response);
if (response?.error) {
//handle error
toast.error(response.error);
} else if (response?.ok) {
//handle response
}
} catch (error) {
if (error instanceof IncorrectPasswordError ) {
console.log("wrong password")
}
toast.error('An error occurred during sign in');
}
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Sign In</CardTitle>
<CardDescription>Sign in to your account.</CardDescription>
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4' >
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem >
<FormLabel>Email</FormLabel>
<FormControl >
<Input type='email' placeholder="alex@gmail.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem >
<FormLabel>password</FormLabel>
<FormControl >
<Input type='password' placeholder="Enter password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full bg-violet-700 hover:bg-violet-900" disabled={isSubmitting || !isValid}>
{isSubmitting ? 'Signing in...' : 'Sign in'}
</Button>
</form>
</Form>
</CardContent>
<CardFooter className='flex flex-col'>
<Link href="/sign-up" className=' text-violet-700 hover:text-violet-900'>Create an account</Link>
</CardFooter>
</Card>
</div>
)
}
and here is the code of the SignIn function (I couldn't use it directly in my sign-in component since it's a client component that's why I put it in a separate file):
interface CredsProps{
email : string,
password: string
}
export async function SignIn(data:CredsProps){
const result = await signIn('credentials', data);
return result;
}
I am out of ideas lol I tried everything I could think of, so I'll let the Professionals give their solutions ;-)