We use cookies to enhance your experience on the site
CodeWorlds

Implementing NextAuth.js / Auth.js

NextAuth.js (now also known as Auth.js) is a comprehensive authentication solution for Next.js applications. It provides ready-made infrastructure for managing sessions, tokens, login, registration, and integration with external authentication providers. In this module, we will cover the detailed implementation of this tool in Next.js applications.

Why use NextAuth.js?

NextAuth.js offers many benefits:

  1. Ready-made solution - no need to implement authentication from scratch
  2. Support for various providers - offers integration with over 50 authentication providers
  3. Security - implements security best practices
  4. Session management - handles sessions on both server and client side
  5. Flexibility - can be customized to specific application needs
  6. App Router support - works with the new Next.js architecture
  7. Extended TypeScript typings - offers full type support

Installation and basic configuration

Let's start by installing NextAuth.js:

1npm install next-auth@latest
2# or
3yarn add next-auth@latest
4# or
5pnpm add next-auth@latest

Basic configuration for the App Router

To configure NextAuth.js in the App Router architecture, we need to create a file to handle the Route API for authentication:

1// app/api/auth/[...nextauth]/route.ts
2import NextAuth from "next-auth";
3import GithubProvider from "next-auth/providers/github";
4
5export const authOptions = {
6  providers: [
7    GithubProvider({
8      clientId: process.env.GITHUB_ID!,
9      clientSecret: process.env.GITHUB_SECRET!
10    })
11  ],
12  // Additional configuration options...
13};
14
15const handler = NextAuth(authOptions);
16export { handler as GET, handler as POST };

This file creates a dynamic API route that handles all necessary authentication endpoints.

Adding the SessionProvider component

To enable the use of the hook

useSession
in client-side components, we need to add
SessionProvider
to our application. It is best to do this in a Provider file or directly in the main layout:

1// app/providers.tsx
2'use client';
3
4import { SessionProvider } from "next-auth/react";
5import { ReactNode } from "react";
6
7export function Providers({ children }: { children: ReactNode }) {
8  return <SessionProvider>{children}</SessionProvider>;
9}
10
11// app/layout.tsx
12import { Providers } from "./providers";
13
14export default function RootLayout({
15  children,
16}: {
17  children: React.ReactNode;
18}) {
19  return (
20    <html lang="en">
21      <body>
22        <Providers>{children}</Providers>
23      </body>
24    </html>
25  );
26}

Environment variables configuration

NextAuth.js requires several environment variables. Create or update the file

.env.local
in the project's main directory:

1# Basic configuration for NextAuth.js
2NEXTAUTH_URL=http://localhost:3000
3NEXTAUTH_SECRET=your_secret_key_for_jwt_encryption
4
5# GitHub authentication data (example)
6GITHUB_ID=your_github_client_id
7GITHUB_SECRET=your_github_client_secret

In a production environment:

  • NEXTAUTH_URL
    should be the full URL of your application
  • NEXTAUTH_SECRET
    should be a long, random string of characters. You can generate it using the command
    openssl rand -base64 32
    .

Configuration of different authentication providers

NextAuth.js supports many authentication providers. Let's look at how to configure a few popular ones:

1. Authentication with GitHub

1import GithubProvider from "next-auth/providers/github";
2
3export const authOptions = {
4  providers: [
5    GithubProvider({
6      clientId: process.env.GITHUB_ID!,
7      clientSecret: process.env.GITHUB_SECRET!,
8      // Optional parameters
9      scope: "read:user user:email", // Specifies access scope
10      profile(profile) {
11        // Customizing user profile
12        return {
13          id: profile.id.toString(),
14          name: profile.name || profile.login,
15          email: profile.email,
16          image: profile.avatar_url,
17          // Additional fields
18          username: profile.login,
19        };
20      },
21    }),
22  ],
23  // Other configuration options...
24};

2. Authentication with Google

1import GoogleProvider from "next-auth/providers/google";
2
3export const authOptions = {
4  providers: [
5    GoogleProvider({
6      clientId: process.env.GOOGLE_CLIENT_ID!,
7      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
8      // Optional parameters
9      authorization: {
10        params: Promise<{
11          prompt: "consent",
12          access_type: "offline",
13          response_type: "code"
14        }>
15      }
16    }),
17  ],
18  // Other configuration options...
19};

3. Authentication with credentials (email/password)

1import CredentialsProvider from "next-auth/providers/credentials";
2import { compare } from "bcrypt";
3import { prisma } from "@/lib/prisma"; // We assume you have a configured Prisma instance
4
5export const authOptions = {
6  providers: [
7    CredentialsProvider({
8      // Name displayed on the login page
9      name: "Credentials",
10      // Login form field configuration
11      credentials: {
12        email: { label: "Email", type: "email" },
13        password: { label: "Password", type: "password" }
14      },
15      async authorize(credentials) {
16        // Check if credentials exist
17        if (!credentials?.email || !credentials?.password) {
18          return null;
19        }
20        
21        try {
22          // Find the user in the database
23          const user = await prisma.user.findUnique({
24            where: { email: credentials.email },
25          });
26          
27          if (!user || !user.password) {
28            return null;
29          }
30          
31          // Check if the password is correct
32          const isPasswordValid = await compare(
33            credentials.password,
34            user.password
35          );
36          
37          if (!isPasswordValid) {
38            return null;
39          }
40          
41          // Return the user object (without password)
42          return {
43            id: user.id,
44            name: user.name,
45            email: user.email,
46            role: user.role,
47            // Other fields you want to include
48          };
49        } catch (error) {
50          console.error("Authentication error:", error);
51          return null;
52        }
53      },
54    }),
55  ],
56  // Other configuration options...
57};

4. Authentication from multiple providers

You can combine multiple authentication providers:

1export const authOptions = {
2  providers: [
3    GithubProvider({
4      clientId: process.env.GITHUB_ID!,
5      clientSecret: process.env.GITHUB_SECRET!,
6    }),
7    GoogleProvider({
8      clientId: process.env.GOOGLE_CLIENT_ID!,
9      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
10    }),
11    CredentialsProvider({
12      // Configuration...
13      async authorize(credentials) {
14        // Authentication logic...
15      },
16    }),
17    // You can add more providers...
18  ],
19  // Other configuration options...
20};

Configuring session and JWT options

NextAuth.js offers different session strategies:

1export const authOptions = {
2  // Provider configuration...
3  
4  session: {
5    // Choose strategy: 'jwt' or 'database'
6    strategy: 'jwt',
7    
8    // Maximum session lifetime (in seconds)
9    maxAge: 30 * 24 * 60 * 60, // 30 days
10    
11    // Session update on each request, extending its validity
12    updateAge: 24 * 60 * 60, // 24 hours
13  },
14  
15  // JWT configuration (only for the 'jwt' strategy)
16  jwt: {
17    // Secret key used to sign JWT tokens
18    // By default uses NEXTAUTH_SECRET
19    secret: process.env.JWT_SECRET,
20    
21    // You can customize JWT encoder/decoder
22    // encode: async ({ secret, token, maxAge }) => {},
23    // decode: async ({ secret, token }) => {},
24  },
25};

Custom authentication pages

By default, NextAuth.js provides simple login pages, but you can replace them with your own:

1export const authOptions = {
2  // Provider configuration...
3  
4  pages: {
5    signIn: '/auth/login',
6    signOut: '/auth/logout',
7    error: '/auth/error', // Authentication error (e.g. invalid credentials)
8    verifyRequest: '/auth/verify-request', // Used for Magic Links
9    newUser: '/auth/new-user' // Page shown after first registration
10  },
11};

Then you need to create these pages in your application. Here is an example login page:

1// app/auth/login/page.tsx
2'use client';
3
4import { signIn, useSession } from 'next-auth/react';
5import { useRouter, useSearchParams } from 'next/navigation';
6import { useState, useEffect } from 'react';
7
8export default function LoginPage() {
9  const router = useRouter();
10  const searchParams = useSearchParams();
11  const callbackUrl = searchParams.get('callbackUrl') || '/dashboard';
12  const { status } = useSession();
13  
14  const [email, setEmail] = useState('');
15  const [password, setPassword] = useState('');
16  const [error, setError] = useState('');
17  const [isLoading, setIsLoading] = useState(false);
18  
19  // Redirect the logged-in user
20  useEffect(() => {
21    if (status === 'authenticated') {
22      router.push(callbackUrl);
23    }
24  }, [status, router, callbackUrl]);
25  
26  // GitHub login handling
27  const handleGitHubSignIn = () => {
28    setIsLoading(true);
29    signIn('github', { callbackUrl });
30  };
31  
32  // Google login handling
33  const handleGoogleSignIn = () => {
34    setIsLoading(true);
35    signIn('google', { callbackUrl });
36  };
37  
38  // Credentials login handling
39  const handleSubmit = async (e: React.FormEvent) => {
40    e.preventDefault();
41    setIsLoading(true);
42    setError('');
43    
44    try {
45      const result = await signIn('credentials', {
46        redirect: false,
47        email,
48        password,
49      });
50      
51      if (result?.error) {
52        setError('Invalid email or password');
53        setIsLoading(false);
54      } else {
55        router.push(callbackUrl);
56      }
57    } catch (error) {
58      setError('An error occurred during login');
59      setIsLoading(false);
60    }
61  };
62  
63  if (status === 'loading') {
64    return <div>Loading...</div>;
65  }
66  
67  return (
68    <div className="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-md">
69      <h1 className="text-2xl font-bold mb-6 text-center">Log in</h1>
70      
71      {error && (
72        <div className="mb-4 p-3 bg-red-100 text-red-700 rounded">
73          {error}
74        </div>
75      )}
76      
77      <div className="space-y-4 mb-6">
78        <button
79          type="button"
80          onClick={handleGitHubSignIn}
81          disabled={isLoading}
82          className="w-full flex items-center justify-center gap-2 p-2 border border-gray-300 rounded-md hover:bg-gray-50"
83        >
84          <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
85            {/* Logo GitHub */}
86            <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />
87          </svg>
88          Continue with GitHub
89        </button>
90        
91        <button
92          type="button"
93          onClick={handleGoogleSignIn}
94          disabled={isLoading}
95          className="w-full flex items-center justify-center gap-2 p-2 border border-gray-300 rounded-md hover:bg-gray-50"
96        >
97          <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
98            {/* Logo Google */}
99            <path d="M12 24C5.385 24 0 18.615 0 12S5.385 0 12 0s12 5.385 12 12-5.385 12-12 12zm0-22.5C6.21 1.5 1.5 6.21 1.5 12S6.21 22.5 12 22.5 22.5 17.79 22.5 12 17.79 1.5 12 1.5zm-5.25 9v3h3v3h3v-3h3v-3h-3v-3h-3v3h-3z" />
100          </svg>
101          Continue with Google
102        </button>
103      </div>
104      
105      <div className="relative my-6">
106        <div className="absolute inset-0 flex items-center">
107          <div className="w-full border-t border-gray-300"></div>
108        </div>
109        <div className="relative flex justify-center text-sm">
110          <span className="px-2 bg-white text-gray-500">
111            or sign in with email
112          </span>
113        </div>
114      </div>
115      
116      <form onSubmit={handleSubmit} className="space-y-4">
117        <div>
118          <label htmlFor="email" className="block text-sm font-medium mb-1">
119            Email
120          </label>
121          <input
122            id="email"
123            type="email"
124            value={email}
125            onChange={(e) => setEmail(e.target.value)}
126            required
127            className="w-full px-3 py-2 border border-gray-300 rounded-md"
128          />
129        </div>
130        
131        <div>
132          <label htmlFor="password" className="block text-sm font-medium mb-1">
133            Password
134          </label>
135          <input
136            id="password"
137            type="password"
138            value={password}
139            onChange={(e) => setPassword(e.target.value)}
140            required
141            className="w-full px-3 py-2 border border-gray-300 rounded-md"
142          />
143        </div>
144        
145        <button
146          type="submit"
147          disabled={isLoading}
148          className="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 disabled:opacity-50"
149        >
150          {isLoading ? 'Logging in...' : 'Log in'}
151        </button>
152      </form>
153    </div>
154  );
155}

Callbacks and events

NextAuth.js allows reacting to various events in the authentication lifecycle through callbacks:

1export const authOptions = {
2  // Provider configuration...
3  
4  callbacks: {
5    // Called when creating a session
6    async session({ session, token }) {
7      // Add token data to the session
8      if (token.sub && session.user) {
9        session.user.id = token.sub;
10      }
11      
12      // Add custom fields, e.g. user role
13      if (token.role && session.user) {
14        session.user.role = token.role as string;
15      }
16      
17      return session;
18    },
19    
20    // Called when creating a JWT
21    async jwt({ token, user, account, profile }) {
22      // Add user data on first login
23      if (user) {
24        token.role = user.role;
25        // Other custom fields...
26      }
27      
28      return token;
29    },
30    
31    // Called after login
32    async signIn({ user, account, profile, email, credentials }) {
33      // Check if the user has access to the application
34      return true; // Return false to deny access
35    },
36    
37    // Called during redirect
38    async redirect({ url, baseUrl }) {
39      // Redirect rules
40      if (url.startsWith(baseUrl)) {
41        return url;
42      }
43      // By default redirect to the base page
44      return baseUrl;
45    },
46  },
47  
48  // Events
49  events: {
50    async signIn(message) {
51      // Called after login
52      console.log('User signed in:', message);
53    },
54    async signOut(message) {
55      // Called after logout
56      console.log('User signed out:', message);
57    },
58    async createUser(message) {
59      // Called after user creation
60      console.log('User created:', message);
61    },
62    async linkAccount(message) {
63      // Called after account linking
64      console.log('Account linked:', message);
65    },
66    async session(message) {
67      // Called after session creation
68      console.log('Session created/updated:', message);
69    },
70  },
71};

Extending types for TypeScript

If you use TypeScript, you can extend NextAuth.js types to include custom fields:

1// In the type extension file (e.g. types/next-auth.d.ts)
2import 'next-auth';
3import 'next-auth/jwt';
4
5declare module 'next-auth' {
6  interface Session {
7    user: {
8      id: string;
9      name?: string | null;
10      email?: string | null;
11      image?: string | null;
12      role?: string;
13      // Add other custom fields...
14    };
15  }
16  
17  interface User {
18    id: string;
19    name?: string | null;
20    email?: string | null;
21    image?: string | null;
22    role?: string;
23    // Add other custom fields...
24  }
25}
26
27declare module 'next-auth/jwt' {
28  interface JWT {
29    role?: string;
30    // Add other custom fields...
31  }
32}

Database adapters

To use the database-based session strategy or store user accounts, NextAuth.js offers adapters for various databases:

Configuration with Prisma

1import { PrismaAdapter } from "@auth/prisma-adapter";
2import { PrismaClient } from "@prisma/client";
3
4const prisma = new PrismaClient();
5
6export const authOptions = {
7  adapter: PrismaAdapter(prisma),
8  providers: [
9    // Provider configuration...
10  ],
11  // Other options...
12};

The Prisma schema should contain the appropriate models:

1// prisma/schema.prisma
2model Account {
3  id                String  @id @default(cuid())
4  userId            String
5  type              String
6  provider          String
7  providerAccountId String
8  refresh_token     String? @db.Text
9  access_token      String? @db.Text
10  expires_at        Int?
11  token_type        String?
12  scope             String?
13  id_token          String? @db.Text
14  session_state     String?
15
16  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
17
18  @@unique([provider, providerAccountId])
19}
20
21model Session {
22  id           String   @id @default(cuid())
23  sessionToken String   @unique
24  userId       String
25  expires      DateTime
26  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
27}
28
29model User {
30  id            String    @id @default(cuid())
31  name          String?
32  email         String?   @unique
33  emailVerified DateTime?
34  image         String?
35  password      String?
36  role          String?   @default("user")
37  accounts      Account[]
38  sessions      Session[]
39}
40
41model VerificationToken {
42  identifier String
43  token      String   @unique
44  expires    DateTime
45
46  @@unique([identifier, token])
47}

Other available adapters

NextAuth.js offers adapters for many databases:

  • SQL adapters: PostgreSQL, MySQL, SQLite, SQL Server
  • Non-relational adapters: MongoDB, DynamoDB
  • ORM/ODM: Prisma, Mongoose, Typeorm, Sequelize, DrizzleORM
  • Others: Firebase, Supabase, Kysely, Planetscale, Xata

Using authentication in the application

1. Fetching session data on the client side

After configuring NextAuth.js, you can use the hook

useSession
in client components:

1'use client';
2
3import { useSession } from 'next-auth/react';
4import { redirect } from 'next/navigation';
5
6export default function Dashboard() {
7  const { data: session, status } = useSession({
8    required: true,
9    onUnauthenticated() {
10      redirect('/auth/login?callbackUrl=/dashboard');
11    },
12  });
13  
14  if (status === 'loading') {
15    return <div>Loading...</div>;
16  }
17  
18  return (
19    <div>
20      <h1>Dashboard</h1>
21      <p>Hello, {session.user.name}!</p>
22      <p>Your email: {session.user.email}</p>
23      {session.user.role === 'admin' && (
24        <div>
25          <h2>Administrator panel</h2>
26          {/* Content only for administrators */}
27        </div>
28      )}
29    </div>
30  );
31}

2. Fetching session data on the server side

In server-rendered components, you can use

getServerSession
:

1// app/profile/page.tsx
2import { getServerSession } from 'next-auth/next';
3import { redirect } from 'next/navigation';
4import { authOptions } from '../api/auth/[...nextauth]/route';
5
6export default async function ProfilePage() {
7  const session = await getServerSession(authOptions);
8  
9  if (!session) {
10    redirect('/auth/login?callbackUrl=/profile');
11  }
12  
13  return (
14    <div>
15      <h1>Profile</h1>
16      <p>Hello, {session.user.name}!</p>
17      <p>Your email: {session.user.email}</p>
18      {/* Rest of the profile content */}
19    </div>
20  );
21}

3. Authentication state management

NextAuth.js provides functions for managing authentication state:

1'use client';
2
3import { signIn, signOut, useSession } from 'next-auth/react';
4
5export function AuthButton() {
6  const { data: session } = useSession();
7  
8  if (session) {
9    return (
10      <button
11        onClick={() => signOut({ callbackUrl: '/' })}
12        className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
13      >
14        Log out
15      </button>
16    );
17  }
18  
19  return (
20    <button
21      onClick={() => signIn()}
22      className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
23    >
24      Log in
25    </button>
26  );
27}

Adapting the UI to NextAuth.js

We can create a component that renders different interface elements depending on the authentication state:

1'use client';
2
3import { useSession } from 'next-auth/react';
4import Link from 'next/link';
5import { AuthButton } from './AuthButton';
6
7export function Navbar() {
8  const { data: session, status } = useSession();
9  const isLoading = status === 'loading';
10  
11  return (
12    <nav className="bg-white shadow-md p-4">
13      <div className="container mx-auto flex justify-between items-center">
14        <Link href="/" className="text-xl font-bold">
15          MyApp
16        </Link>
17        
18        <div className="flex items-center space-x-4">
19          <Link href="/" className="hover:text-blue-500">
20            Home page
21          </Link>
22          
23          {session ? (
24            <>
25              <Link href="/dashboard" className="hover:text-blue-500">
26                Dashboard
27              </Link>
28              <Link href="/profile" className="hover:text-blue-500">
29                Profile
30              </Link>
31              {session.user.role === 'admin' && (
32                <Link href="/admin" className="hover:text-blue-500">
33                  Admin
34                </Link>
35              )}
36            </>
37          ) : (
38            <Link href="/features" className="hover:text-blue-500">
39              Features
40            </Link>
41          )}
42          
43          {!isLoading && (
44            <div className="flex items-center space-x-2">
45              {session && (
46                <div className="flex items-center">
47                  {session.user.image && (
48                    <img
49                      src={session.user.image}
50                      alt={session.user.name || 'User'}
51                      className="w-8 h-8 rounded-full mr-2"
52                    />
53                  )}
54                  <span className="text-sm font-medium">
55                    {session.user.name}
56                  </span>
57                </div>
58              )}
59              <AuthButton />
60            </div>
61          )}
62        </div>
63      </div>
64    </nav>
65  );
66}

Authentication with API Routes

NextAuth.js can also be used to secure API endpoints:

1// app/api/protected-data/route.ts
2import { getServerSession } from 'next-auth/next';
3import { NextResponse } from 'next/server';
4import { authOptions } from '../auth/[...nextauth]/route';
5
6export async function GET() {
7  const session = await getServerSession(authOptions);
8  
9  if (!session) {
10    return NextResponse.json(
11      { message: 'Unauthorized access' },
12      { status: 401 }
13    );
14  }
15  
16  // Check permissions based on role (optional)
17  if (session.user.role !== 'admin') {
18    return NextResponse.json(
19      { message: 'Insufficient permissions' },
20      { status: 403 }
21    );
22  }
23  
24  // Data available only to logged-in users (and admins if we check roles)
25  return NextResponse.json({
26    message: 'Success',
27    data: {
28      secretContent: 'This data is protected',
29      timestamp: new Date().toISOString()
30    }
31  });
32}

Best practices for implementing NextAuth.js

  1. Security first

    • Use HTTPS in production
    • Generate a strong NEXTAUTH_SECRET
    • Never store sensitive data in JWT tokens
  2. Performance optimization

    • Use session strategies appropriate to your needs
    • Consider using the JWT strategy for greater scalability
  3. Error handling

    • Implement custom error pages
    • Log authentication errors for analysis
  4. User experience

    • Design user-friendly login forms
    • Allow smooth switching between providers
  5. Testing

    • Test different authentication scenarios
    • Check how the application reacts to expired sessions

Advanced scenarios

1. Multi-step authentication

In some cases, you may want to implement multi-step authentication (e.g. password + SMS code):

1// Implement a custom CredentialsProvider
2CredentialsProvider({
3  name: 'TwoFactor',
4  credentials: {
5    email: { label: "Email", type: "email" },
6    password: { label: "Password", type: "password" },
7    code: { label: "Verification code", type: "text" }
8  },
9  async authorize(credentials) {
10    if (!credentials?.email || !credentials?.password) {
11      return null;
12    }
13    
14    // First step: verify password
15    const user = await prisma.user.findUnique({
16      where: { email: credentials.email }
17    });
18    
19    if (!user || !await compare(credentials.password, user.password)) {
20      return null;
21    }
22    
23    // Second step: verify the verification code
24    if (credentials.code) {
25      const isValidCode = await verifyTwoFactorCode(user.id, credentials.code);
26      
27      if (!isValidCode) {
28        return null;
29      }
30    } else {
31      // If the code was not provided and is required
32      if (user.twoFactorEnabled) {
33        // Sending the code to email/phone
34        await sendTwoFactorCode(user);
35        
36        // Return a special object so App Router knows we need a code
37        throw new Error('TwoFactorRequired');
38      }
39    }
40    
41    // Authentication successful
42    return {
43      id: user.id,
44      email: user.email,
45      name: user.name,
46      role: user.role
47    };
48  }
49})

2. Implementing custom registration logic

1// app/api/register/route.ts
2import { NextResponse } from 'next/server';
3import { hash } from 'bcrypt';
4import { prisma } from '@/lib/prisma';
5import { z } from 'zod';
6
7// Validation schema
8const userSchema = z.object({
9  name: z.string().min(2, 'First name must be at least 2 characters'),
10  email: z.string().email('Invalid email address'),
11  password: z.string().min(8, 'Password must be at least 8 characters')
12});
13
14export async function POST(request: Request) {
15  try {
16    const body = await request.json();
17    
18    // Validate data
19    const result = userSchema.safeParse(body);
20    
21    if (!result.success) {
22      return NextResponse.json(
23        { message: result.error.errors[0].message },
24        { status: 400 }
25      );
26    }
27    
28    const { name, email, password } = result.data;
29    
30    // Check if user already exists
31    const existingUser = await prisma.user.findUnique({
32      where: { email }
33    });
34    
35    if (existingUser) {
36      return NextResponse.json(
37        { message: 'A user with this email address already exists' },
38        { status: 409 }
39      );
40    }
41    
42    // Hash the password
43    const hashedPassword = await hash(password, 10);
44    
45    // Create new user
46    const user = await prisma.user.create({
47      data: {
48        name,
49        email,
50        password: hashedPassword,
51        role: 'user' // Default role
52      }
53    });
54    
55    // Do not return password in response
56    const { password: _, ...userWithoutPassword } = user;
57    
58    return NextResponse.json(
59      { message: 'Registration completed successfully', user: userWithoutPassword },
60      { status: 201 }
61    );
62  } catch (error) {
63    console.error('Registration error:', error);
64    return NextResponse.json(
65      { message: 'A server error occurred' },
66      { status: 500 }
67    );
68  }
69}

Summary

NextAuth.js (Auth.js) is a comprehensive tool for implementing authentication in Next.js applications. It offers:

  1. Flexibility - supports multiple authentication strategies
  2. Integration - easily integrates with various providers
  3. Security - implements best security practices
  4. Scalability - works with various databases and architectures
  5. Extensibility - can be adapted to specific needs

Thanks to NextAuth.js, you can quickly implement a complete authentication system in your Next.js application, saving time and minimizing the risk of security-related errors.

In the following modules, we will discuss more advanced aspects of authentication, including authentication strategies, session and token management, and implementation of roles and permissions.

Go to CodeWorlds