Modern Web Authentication with Next.js, NextAuth.js, and Prisma
Introduction
Authentication is a cornerstone of modern web applications. Next.js, combined with NextAuth.js and Prisma, provides a powerful and elegant solution for implementing secure authentication flows. This article guides you through setting up a basic authentication system using these technologies.
Prerequisites
- Node.js installed
- Basic understanding of React and Next.js
Setting up the Next.js Project
-
Create a new Next.js project:
npx create-next-app my-auth-app cd my-auth-app
-
Install NextAuth.js and Prisma:
npm install next-auth @prisma/client npm install -D prisma
Configuring Prisma
-
Initialize Prisma:
npx prisma init
This creates a
prisma
directory with aschema.prisma
file. -
Define your user model in
schema.prisma
:generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } model User { id String @id @default(uuid()) name String? email String? @unique password String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
-
Update your
.env
file with the database URL:DATABASE_URL="file:./dev.db"
-
Run migrations:
npx prisma migrate dev --name init npx prisma generate
Configuring NextAuth.js
-
Create a
[...nextauth].js
file inpages/api/auth
:// pages/api/auth/[...nextauth].js import NextAuth from "next-auth" import CredentialsProvider from "next-auth/providers/credentials" import { PrismaClient } from "@prisma/client" import bcrypt from 'bcryptjs'; const prisma = new PrismaClient(); export default NextAuth({ providers: [ CredentialsProvider({ name: "credentials", async authorize(credentials, req) { const user = await prisma.user.findUnique({ where: { email: credentials.email, }, }); if (!user) { throw new Error("No user found with this email"); } const isPasswordValid = await bcrypt.compare(credentials.password, user.password); if (!isPasswordValid) { throw new Error("Invalid password"); } return { id: user.id, email: user.email, name: user.name }; }, }), ], session: { strategy: "jwt", }, secret: process.env.NEXTAUTH_SECRET, });
Remember to install bcrypt
npm install bcryptjs
and generate a secureNEXTAUTH_SECRET
usingopenssl rand -base64 32
and add it to your.env
file. -
Wrap your
_app.js
withSessionProvider
:// pages/_app.js import { SessionProvider } from "next-auth/react" function MyApp({ Component, pageProps: { session, ...pageProps } }) { return ( <SessionProvider session={session}> <Component {...pageProps} /> </SessionProvider> ) } export default MyApp
Building the UI
-
Create a simple login form:
// components/LoginForm.js import { useState } from 'react'; import { signIn } from 'next-auth/react'; function LoginForm() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleSubmit = async (e) => { e.preventDefault(); const result = await signIn('credentials', { email, password, redirect: false, }); if (result?.error) { console.error("Login error:", result.error); } }; return ( <form onSubmit={handleSubmit}> <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} /> <button type="submit">Login</button> </form> ); } export default LoginForm;
-
Use
useSession
hook to manage user session in your pages:// pages/index.js import { useSession, signIn, signOut } from "next-auth/react" import LoginForm from "../components/LoginForm"; export default function Home() { const { data: session } = useSession() if (session) { return ( <> Signed in as {session.user.email} <br /> <button onClick={() => signOut()}>Sign out</button> </> ) } return ( <> Not signed in <br /> <LoginForm /> <button onClick={() => signIn()}>Sign in</button> </> ) }
Conclusion
This article provides a basic setup for authentication with Next.js, NextAuth.js, and Prisma. Further customization and security enhancements, such as adding validation and more robust error handling, are encouraged.