I've been the last 12 hours on the same issue and have ready probably all the forums online... nothing really fits my need.
I have followed the NextAuth and NextJS learn tutorial to set up authentication, unfortunately, I want to redirect the user after a successful login to the page /dashboard, but by default it goes to /login and cannot change it, I tried to change the middleware logic, try to revalidate the path, and tried to understand how this works.
loginForm.ts
'use client';
import { useFormState, useFormStatus } from 'react-dom';
import Link from 'next/link';
import { authenticate } from '@/app/actions/login/loginEngine';
import ErrorAlert from '../errorAlert';
import { Button } from '../button';
export default function LoginForm() {
const [errorMessage, dispatch] = useFormState(authenticate, undefined);
return (
<>
{errorMessage && (
<ErrorAlert AlertMessage={errorMessage} />
)}
<div className="flex min-h-full flex-1 items-center justify-center px-4 py-12 sm:px-6 lg:px-8">
<div className="w-full max-w-sm space-y-10">
<div>
{/* Include your entreprise logo here */}
<h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
Sign in to your account
</h2>
</div>
<form className="space-y-6" action={dispatch}>
<div className="relative -space-y-px rounded-md shadow-sm">
<div className="pointer-events-none absolute inset-0 z-10 rounded-md ring-1 ring-inset ring-gray-300" />
<div>
<label htmlFor="email-address" className="sr-only">
Email address
</label>
<input
id="email-address"
name="email"
type="email"
autoComplete="email"
required
minLength={6}
className="relative block w-full rounded-t-md border-0 pl-4 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-100 placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
placeholder="Email address"
/>
</div>
<div>
<label htmlFor="password" className="sr-only">
Password
</label>
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
className="relative block w-full rounded-b-md border-0 pl-5 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-100 placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
placeholder="Password"
/>
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center">
<input
id="remember-me"
name="remember-me"
type="checkbox"
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"
/>
<label htmlFor="remember-me" className="ml-3 block text-sm leading-6 text-gray-900">
Remember me
</label>
</div>
<div className="text-sm leading-6">
<a href="#" className="font-semibold text-indigo-600 hover:text-indigo-500">
Forgot password?
</a>
</div>
</div>
<LoginButton />
</form>
<p className="text-center text-sm leading-6 text-gray-500">
Not a member?{' '} <Link href="/register" className="font-semibold leading-6 text-indigo-600 hover:text-indigo-500">Create an account</Link>
</p>
</div>
</div>
</>
)
}
function LoginButton() {
const { pending } = useFormStatus();
return (
<div>
<Button className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
aria-disabled={pending}>
Sign in
</Button>
<button >
Log in
</button>
</div>
);
}
loginEngine.ts
'use server'
import { AuthError } from 'next-auth';
import { signIn } from '../../../auth';
import { revalidatePath } from 'next/cache';
export async function authenticate(
prevState: string | undefined,
formData: FormData,
) {
try {
await signIn('credentials', formData);
} catch (error) {
if (error instanceof AuthError) {
switch (error.type) {
case 'CredentialsSignin':
return 'Invalid credentials.';
default:
return 'Something went wrong.';
}
}
throw error;
}
}
auth.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
import { z } from 'zod';
import bcrypt from 'bcrypt';
import { getUser } from '@/app/libs/dbCore';
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
.safeParse(credentials);
if (parsedCredentials.success) {
const { email, password } = parsedCredentials.data;
const user = await getUser(email);
if (!user) return null;
const passwordsMatch = await bcrypt.compare(password, user.password);
if (passwordsMatch) return user;
}
console.log('Invalid credentials');
return null;
},
}),
],
});
auth.config.ts
import { NextAuthConfig } from "next-auth";
export const authConfig = {
pages: {
signIn: '/login',
signOut: '/'
},
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
const isOnSettings = nextUrl.pathname.startsWith('/settings')
if (isOnDashboard || isOnSettings) {
if (isLoggedIn) return true;
return false; // Redirect unauthenticated users to login page
} else if (isLoggedIn) {
return Response.redirect(new URL('/dashboard', nextUrl));
}
return true;
},
},
providers: [], // Add providers with an empty array for now
} satisfies NextAuthConfig;
middleware.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
export default NextAuth(authConfig).auth;
export const config = {
// https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
};
Thank you
I know there is the callback option https://next-auth.js.org/getting-started/client#specifying-a-callbackurl but does not fit my need... I am using a different logic in my form... I start to be desperate.
In your loginEngine.ts , replace:
await signIn('credentials', formData);
With:
await signIn("credentials", {
formData,
redirectTo:'/dashboard'
})