How to create an Authentication & Authorization feature in Next JS 14?
Published: 2024-06-04 05:41:14

Creating an authentication and authorization feature in Next.js 14 involves several steps. Here’s a comprehensive guide to help you implement it:

### Step 1: Set Up a New Next.js Project
1. **Initialize a New Project**:
   ```bash
   npx create-next-app@latest my-nextjs-app
   cd my-nextjs-app
   ```

2. **Install Required Packages**:
   ```bash
   npm install next-auth mongoose bcryptjs
   ```

### Step 2: Configure MongoDB
1. **Create a `.env.local` File**:
   ```plaintext
   MONGODB_URI=mongodb://localhost:27017/auth-db
   NEXTAUTH_URL=http://localhost:3000
   NEXTAUTH_SECRET=your_secret_key
   ```

### Step 3: Set Up Mongoose Models
1. **Create a Directory Structure**:
   ```bash
   mkdir -p src/models src/lib
   ```

2. **Create User Model**:
   Create `src/models/User.js`:
   ```javascript
   import mongoose from 'mongoose';

   const UserSchema = new mongoose.Schema({
     name: {
       type: String,
       required: true,
     },
     email: {
       type: String,
       required: true,
       unique: true,
     },
     password: {
       type: String,
       required: true,
     },
   });

   UserSchema.pre('save', async function(next) {
     if (!this.isModified('password')) {
       return next();
     }
     const salt = await bcrypt.genSalt(10);
     this.password = await bcrypt.hash(this.password, salt);
     next();
   });

   export default mongoose.models.User || mongoose.model('User', UserSchema);
   ```

3. **Create MongoDB Connection Utility**:
   Create `src/lib/mongodb.js`:
   ```javascript
   import mongoose from 'mongoose';

   const connectDB = async () => {
     if (mongoose.connections[0].readyState) {
       return;
     }
     await mongoose.connect(process.env.MONGODB_URI, {
       useNewUrlParser: true,
       useUnifiedTopology: true,
     });
   };

   export default connectDB;
   ```

### Step 4: Configure NextAuth.js
1. **Create the Auth API Route**:
   Create `src/pages/api/auth/[...nextauth].js`:
   ```javascript
   import NextAuth from 'next-auth';
   import Providers from 'next-auth/providers';
   import bcrypt from 'bcryptjs';
   import connectDB from '../../../lib/mongodb';
   import User from '../../../models/User';

   export default NextAuth({
     providers: [
       Providers.Credentials({
         async authorize(credentials) {
           await connectDB();
           const user = await User.findOne({ email: credentials.email });

           if (user && (await bcrypt.compare(credentials.password, user.password))) {
             return { name: user.name, email: user.email };
           }

           throw new Error('Invalid email or password');
         },
       }),
     ],
     database: process.env.MONGODB_URI,
     secret: process.env.NEXTAUTH_SECRET,
     session: {
       jwt: true,
     },
     callbacks: {
       async jwt(token, user) {
         if (user) {
           token.id = user.id;
         }
         return token;
       },
       async session(session, token) {
         session.user.id = token.id;
         return session;
       },
     },
   });
   ```

### Step 5: Set Up Registration API Route
1. **Create Registration API Route**:
   Create `src/pages/api/auth/register.js`:
   ```javascript
   import connectDB from '../../../lib/mongodb';
   import User from '../../../models/User';

   export default async function handler(req, res) {
     if (req.method === 'POST') {
       await connectDB();

       const { name, email, password } = req.body;

       const userExists = await User.findOne({ email });

       if (userExists) {
         return res.status(400).json({ message: 'User already exists' });
       }

       const user = await User.create({ name, email, password });

       if (user) {
         res.status(201).json({
           _id: user._id,
           name: user.name,
           email: user.email,
         });
       } else {
         res.status(400).json({ message: 'Invalid user data' });
       }
     } else {
       res.status(405).json({ message: 'Method not allowed' });
     }
   }
   ```

### Step 6: Set Up Client-Side Authentication
1. **Create a Registration Page**:
   Create `src/pages/register.js`:
   ```javascript
   import { useState } from 'react';
   import { useRouter } from 'next/router';

   const Register = () => {
     const [name, setName] = useState('');
     const [email, setEmail] = useState('');
     const [password, setPassword] = useState('');
     const [error, setError] = useState('');
     const router = useRouter();

     const handleSubmit = async (e) => {
       e.preventDefault();

       const res = await fetch('/api/auth/register', {
         method: 'POST',
         headers: {
           'Content-Type': 'application/json',
         },
         body: JSON.stringify({ name, email, password }),
       });

       if (res.ok) {
         router.push('/login');
       } else {
         const data = await res.json();
         setError(data.message);
       }
     };

     return (
       <form onSubmit={handleSubmit}>
         <div>
           <label>Name</label>
           <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
         </div>
         <div>
           <label>Email</label>
           <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
         </div>
         <div>
           <label>Password</label>
           <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
         </div>
         {error && <p>{error}</p>}
         <button type="submit">Register</button>
       </form>
     );
   };

   export default Register;
   ```

2. **Create a Login Page**:
   Create `src/pages/login.js`:
   ```javascript
   import { useState } from 'react';
   import { signIn } from 'next-auth/react';
   import { useRouter } from 'next/router';

   const Login = () => {
     const [email, setEmail] = useState('');
     const [password, setPassword] = useState('');
     const [error, setError] = useState('');
     const router = useRouter();

     const handleSubmit = async (e) => {
       e.preventDefault();

       const result = await signIn('credentials', {
         redirect: false,
         email,
         password,
       });

       if (result.ok) {
         router.push('/');
       } else {
         setError(result.error);
       }
     };

     return (
       <form onSubmit={handleSubmit}>
         <div>
           <label>Email</label>
           <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
         </div>
         <div>
           <label>Password</label>
           <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
         </div>
         {error && <p>{error}</p>}
         <button type="submit">Login</button>
       </form>
     );
   };

   export default Login;
   ```

3. **Protecting Pages (Client-Side)**:
   Create a higher-order component (HOC) to protect pages:
   ```javascript
   import { useSession } from 'next-auth/react';
   import { useRouter } from 'next/router';
   import { useEffect } from 'react';

   const withAuth = (WrappedComponent) => {
     return (props) => {
       const { data: session, status } = useSession();
       const router = useRouter();

       useEffect(() => {
         if (status === 'unauthenticated') {
           router.replace('/login');
         }
       }, [status, router]);

       if (status === 'authenticated') {
         return <WrappedComponent {...props} />;
       }

       return null;
     };
   };

   export default withAuth;
   ```

   Usage:
   ```javascript
   import withAuth from '../path/to/withAuth';

   const ProtectedPage = () => {
     return (
       <div>
         <h1>Protected Content</h1>
       </div>
     );
   };

   export default withAuth(ProtectedPage);
   ```

### Step 7: Start the Application
1. **Run the Next.js Application**:
   ```bash
   npm run dev
   ```

### Step 8: Test the Application
1. **Register a User**:
   - Navigate to `/register` and create a new user.

2. **Login a User**:
   - Navigate to `/login` and log in with the newly created user.

3. **Access Protected Page**:
   - Navigate to a protected page, e.g., `/profile`, to ensure it redirects to `/login` when not authenticated and displays the content when authenticated.

This guide provides a foundational approach to implementing authentication and authorization in a Next.js 14 application. You can further expand and customize it based on your application's requirements.

Disclaimer: This content is generated by AI.