
Auth has always been one of the most annoying parts of any web app.
But with Next-Auth v5, things got way easier and cleaner β especially if youβre using Next.js 14 with the new App Router.
Now, we separate auth config and the NextAuth handler, which makes the code cleaner, easier to maintain, and more scalable.
Install dependencies:
npm install next-auth @next-auth/prisma-adapter bcryptjs @prisma/client
Update your schema.prisma User model:
model User { id String @id @default(cuid()) name String? username String? @unique email String? @unique password String emailVerified DateTime? avatarUrl String? bio String? role String @default("USER") accounts Account[] sessions Session[] }
Run migrations:
npx prisma migrate dev
auth.config.tsimport { type NextAuthConfig } from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import prisma from '@lib/prisma'
import bcrypt from 'bcryptjs'
import { PrismaAdapter } from '@next-auth/prisma-adapter'
export const authConfig: NextAuthConfig = {
adapter: PrismaAdapter(prisma),
providers: [
Credentials({
name: 'Credentials',
id: 'credentials',
credentials: {
identifier: { label: 'Email or Username', type: 'text', placeholder: 'user or user@gmail.com' },
password: { label: 'Password', type: 'password', placeholder: '******' },
},
async authorize(credentials) {
const { password, identifier } = credentials
(!password || !identifier) ()
user = prisma..({
: { : [{ : identifier }, { : identifier }] },
})
(!user) ()
match = bcrypt.(password, user.)
(!match) ()
{
: user.,
: user.,
: user. | ,
: user.!,
: user.,
: user.,
: ,
}
},
}),
],
: {
() {
(user) {
token. = user.
token. = user.
token. = user.
token. = user.
}
token
},
() {
(token) {
session. = {
: token. ,
: token. ,
: token. ,
: token. ,
: token. | ,
: ,
}
}
session
},
},
}
auth.tsimport NextAuth from 'next-auth'
import { authConfig } from './auth.config'
export const { handlers, auth, signIn, signOut } = NextAuth({
...authConfig,
pages: { signIn: '/login' },
session: { strategy: 'jwt', maxAge: 7 * 24 * 60 * 60 },
jwt: { maxAge: 7 * 24 * 60 * 60 },
secret: process.env.AUTH_SECRET,
})
useSession'use client'
import { useSession, signIn, signOut } from "@/app/auth"
export default function Header() {
const { data: session } = useSession()
return (
<header className="flex items-center justify-between p-4 border-b">
{session ? (
<>
<p>Hi, {session.user?.username}</p>
<button onClick={() => signOut()}>Logout</button>
</>
) : (
<button onClick={() => signIn()}>Login</button>
)}
</header>
)
}
import { auth } from "@/app/auth"
import { redirect } from "next/navigation"
export default async function DashboardPage() {
const session = await auth()
if (!session) redirect("/login")
return <h1>Welcome back, {session.user?.username}!</h1>
}
This setup gives you:
No more messy auth files or hacks π
Perfect for modern Next.js 14 apps using Next-Auth v5.