Back to articles

Modern Web Authentication with Next.js, NextAuth.js, and Prisma

AuthorMajd Muhtaseb05/30/20258 minutes
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

  1. Create a new Next.js project:

    npx create-next-app my-auth-app
    cd my-auth-app
    
  2. Install NextAuth.js and Prisma:

    npm install next-auth @prisma/client
    npm install -D prisma
    

Configuring Prisma

  1. Initialize Prisma:

    npx prisma init
    

    This creates a prisma directory with a schema.prisma file.

  2. 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
    }
    
  3. Update your .env file with the database URL:

    DATABASE_URL="file:./dev.db"
    
  4. Run migrations:

    npx prisma migrate dev --name init
    npx prisma generate
    

Configuring NextAuth.js

  1. Create a [...nextauth].js file in pages/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 secure NEXTAUTH_SECRET using openssl rand -base64 32 and add it to your .env file.

  2. Wrap your _app.js with SessionProvider:

    // 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

  1. 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;
    
  2. 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.