I am new to auth.js (previously next-auth) and I am trying to implement it in my Next14 project with src and app directories. The folder structure of my app is like below:
I have used Prisma adapter with Auth.js and Prisma is connected to my MongoDB database
Now, currently I am facing two problems:
What can I try next?
Firstly for logout not working issue
Navbar.tsx (directly used in layout)
import { auth, signOut } from "@/auth";
import Image from "next/image";
import Link from "next/link";
import { BsPersonCircle } from "react-icons/bs";
export default async function Navbar() {
const session = await auth();
return (
<div className="navbar bg-base-200 h-[10vh] px-[60px] py-[20px]">
<Link href={"/"} className="flex-1">
<Image src="/logo.png" alt="logo" width={200} height={200} />
</Link>
<div className="dropdown dropdown-end">
<div className="flex items-center gap-3">
<p>Hello {session?.user?.name ? session?.user?.name : "stranger"}</p>
<div tabIndex={0} role="button" className="btn btn-ghost btn-circle">
<BsPersonCircle size={30} color="#E84644" />
</div>
</div>
<ul
tabIndex={0}
className="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52 border border-primary"
>
<li>
<Link href={"/profile"} className="justify-between">
Profile
<span className="badge">New</span>
</Link>
</li>
<li>
<a>Settings</a>
</li>
<li>
<form action={async () => {
"use server";
await signOut();
}}>
<button type="submit">
Logout
</button>
</form>
</li>
</ul>
</div>
</div>
);
}
auth.ts
import NextAuth from "next-auth";
import authConfig from "@/auth.config";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { db } from "./lib/db";
export const { handlers, auth, signIn, signOut } = NextAuth({
debug: true,
adapter: PrismaAdapter(db),
session: { strategy: "jwt" },
...authConfig,
});
auth.config.ts
import GitHub from "next-auth/providers/github";
import type { NextAuthConfig } from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { LoginSchema } from "./schemas";
import { getUserByEmail } from "./data/user";
import brcypt from "bcryptjs";
export default {
providers: [
GitHub,
Credentials({
authorize: async (credentials) => {
const validatedFields = LoginSchema.safeParse(credentials);
if(validatedFields.success) {
const { email, password } = validatedFields.data;
const user = await getUserByEmail(email);
if(!user || !user.password) return null;
const passwordsMatch = await brcypt.compare(password, user.password);
if(passwordsMatch) {
return user;
}
}
return null;
}
})
],
} satisfies NextAuthConfig;
Secondly for login getting stuck issue
app > auth > login > page.tsx
"use client";
import LoginForm from "@/Components/Common/Auth/LoginForm";
import AuthWrapper from "@/Components/Common/Auth/AuthWrapper";
import { useTransition } from "react";
export default function Page() {
const [isPending, startTransition] = useTransition();
return (
<div>
<AuthWrapper register={false} isPending={isPending}>
<LoginForm isPending={isPending} startTransition={startTransition} />
</AuthWrapper>
</div>
)
}
LoginForm.tsx
import React, { useState } from 'react';
import { useForm } from "react-hook-form";
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from "zod";
import { LoginSchema } from '@/schemas';
import FormToast from './FormToast';
import { login } from '@/actions/login';
export default function LoginForm({ isPending, startTransition }: { isPending: boolean, startTransition: any }) {
const { register, handleSubmit, formState: { errors } } = useForm<z.infer<typeof LoginSchema>>({
resolver: zodResolver(LoginSchema),
defaultValues: {
email: "",
password: "",
},
});
const [feedback, setFeedback] = useState<{
error?: boolean
msg?: string
}>();
const onSubmit = async (data: z.infer<typeof LoginSchema>) => {
startTransition(async () => {
setFeedback({
error: undefined,
msg: undefined,
});
const result: { error: boolean, msg: string } = await login(data);
if(result) {
setFeedback(result);
}
});
};
return (
<div className="grid grid-cols-1 gap-5 w-[30vw]">
<form onSubmit={handleSubmit(onSubmit)} className="grid grid-cols-1 gap-5">
<div className="col-span-1">
<label className="input input-bordered input-md flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" className="w-4 h-4 opacity-70">
<path d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z" />
<path d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z" />
</svg>
<input
type="text"
className="grow"
placeholder="Email"
{...register("email")}
disabled={isPending}
/>
</label>
{errors.email && <ErrorMsg msg={errors.email.message} />}
</div>
<div className="col-span-1">
<label className="input input-bordered input-md flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" className="w-4 h-4 opacity-70">
<path fillRule="evenodd" d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z" clipRule="evenodd" />
</svg>
<input
type="password"
className="grow"
placeholder="Password"
{...register("password")}
disabled={isPending}
/>
</label>
{errors.password && <ErrorMsg msg={errors.password.message} />}
</div>
<FormToast {...feedback} />
<button disabled={isPending} type="submit" className="col-span-1 btn btn-primary text-white">Login</button>
</form>
</div>
);
}
function ErrorMsg( { msg }: { msg: string | undefined }) {
return (
<p className="text-primary text-sm text-left mt-1">{msg}</p>
)
}
I am using server actions for login
src > actions > login.ts
"use server";
import { signIn } from "@/auth";
import { DEFAULT_LOGIN_REDIRECT } from "@/routes";
import { LoginSchema } from "@/schemas";
import { AuthError } from "next-auth";
import * as z from "zod";
export const login = async (values: z.infer<typeof LoginSchema>) => {
const validatedFields = LoginSchema.safeParse(values);
if (!validatedFields.success) {
return { error: true, msg: "Invalid fields" };
}
const { email, password } = validatedFields.data;
try {
await signIn("credentials", {
email,
password,
});
return { error: false, msg: "Logged in successfully!" }
} catch (error) {
if (error instanceof AuthError) {
switch (error.type) {
case "CredentialsSignin": return {
error: true,
msg: "Invalid credentials"
}
default: return { error: true, msg: "Something went wrong!" }
}
}
throw error;
}
}
I am also providing the middleware just in case i did something wrong here middleware.ts
import authConfig from "@/auth.config"
import NextAuth from "next-auth"
import { DEFAULT_LOGIN_REDIRECT, apiAuthPrefix, authRoutes, publicRoutes } from "./routes"
const { auth } = NextAuth(authConfig)
export default auth((req) => {
const { nextUrl } = req;
const isLoggedIn = !!req.auth;
const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix);
const isPublicRoute = publicRoutes.includes(nextUrl.pathname);
const isAuthRoute = authRoutes.includes(nextUrl.pathname);
if (isApiAuthRoute) {
return;
}
if (isAuthRoute) {
if (isLoggedIn) {
return Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl));
}
return;
}
if (!isLoggedIn && !isPublicRoute) {
return Response.redirect(new URL("/auth/login", nextUrl));
}
return;
})
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico|logo.png|logoVertical.png|logoVertical2.png).*)"],
}
So if someone is using middlewares like me to handle redirections after login and logout the only solution to the problems here is:
I still don't understand why does the redirection was not happening previously in both cases even though the logs were showing GET request.