Integrating Credentials Provider with NextAuthJS and MongoDB with Typescript - Part 1
Tags: mongodb, nextjs, typescript, nextauth
Published: 2023-04-08 08:45:43

 Authentication and Authorization in modern application is very essentials because you are managing user data, but it can be a bit sketchy sometimes as we have to consider route protection, session management, protecting several routes/pages, password encryption, validating user's credentials during sign-up and sign-in. 

Next Auth is an open source, free library that handles authentication tasks for the developer like JWT, cookie. It also enables OAUTH2.0 providers like Twitter, GitHub, Google, Facebook and Discord.

Next Auth handles session management and this improves the security of your application by implementing built in security features so that the server can't be tricked easily 

In this tutorials, we are going to look at how to implement email password authentication on users' credentials like email and password.

### Table Of Contents
        * [Create NextJS App](#chapter-1)
        * [Installing Dependencies](#chapter-2)
        * [User Sign Up](#chapter-3)
        * [Sign In](#chapter-4)
### Packages to Install
 * ``mongoose``
 * ``next-auth``
 * ``bycryptjs``


### Creating The NextJS Application <a name="chapter-1"></a>
I will be using ``pnpm`` but be free to use ``npm`` if you like. Go to your terminal and run the following line of code 
```
$pnpm create next-app --typescript
```
We Initialize the application with typescript
Follow the Prompt on the command line to create the NextJS Application. I will not be using the app directory that comes with [``NextJS 13``](https://nextjs.org/docs/getting-started), I will also be using the ``src`` directory with NextJS, but fill free to use it to your own liking.

### Installing Dependencies <a name="chapter-2"></a>
After successfully creating the nextJS application, we install the dependencies, go to your terminal and run
```
$pnpm add mongoose next-auth bcryptjs
```
We will be using a local instance of mongodb server, so we fetch the URL
I will not show how to install mongodb on your pc, feel free to use the database you like
Create a ``.env.development`` file in the root folder, then paste the following line of code
``MONGODB_URL="mongodb://localhost:27017/test_db"``

### Model and Controller <a name="chapter-3"></a>
NextJS provides us to write API codes in the ``pages/api`` folder using the NodeJS environment. It will also follow the same folder-structured route. Create a folder in the ``api`` folder and call it ``auth``. Create a route ``pages/api/auth/sign-up.ts``. We also need to make sure that only the POST method is accepted and nothing else.
 
But before we start working the the route, let's create a user model that we can use to define the components of user
Create a folder called `utils`. Inside the folder, create a file called `user.model.ts`. Then paste the following line of code
```
import mongoose, { Document } from 'mongoose';

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

export interface IUser extends Document {
    name: string;
    age: number;
    email: string;
    password: string;
    createdAt: Date;
    updatedAt: Date;
}
let UserModel: mongoose.Model<IUser>;
try {
  // Try to get the existing model from mongoose
  UserModel = mongoose.model<IUser>('User');
} catch {
  // If the model doesn't exist, define it
  UserModel = mongoose.model<IUser>('User', userSchema);
}
export default UserModel;
```
Here, we have defined the user model and its contents, we also export the user Interface so that we can use it somewhere else. 

We also need to handle sign in to mongodb database. Let's create a file and call it ``db.ts``. Paste in the following line of code

```
import mongoose from "mongoose";
//connect to mongodb
const connectionUrl = "mongodb://localhost:27017/test_db";
const connectDB = async () => {
    try {
        const conn = await mongoose.connect(process.env.MONGODB_URI||connectionUrl);
        console.log(`MongoDB Connected: ${conn.connection.host}`);
    } catch (err) {
        console.error('Error Encountered: ',err);
        process.exit(1);
    }
};
export default connectDB;
```

Now lets create a controller file inside the ``utils`` folder, name it ``user.controller.ts``. Now lets paste the following code.

```
import UserModel from './user.model';
type UserX = Omit<IUser,  | 'createdAt' | 'updatedAt' >;
export const createUser = async (user: UserX) => {
    console.log('Request to add user: ', user)
    console.log(await connectDB());
    const userAdded: UserX = {
        ...user,
        password: bcryptjs.hashSync(user.password, 10),
        userType: 'PATIENT',
    }
    console.log('Request to add user: ', userAdded);
    const finduser = await UserModel.findOne({
        email: userAdded.email
    });
    if(finduser) throw new Error("User Found, Change Email");
    const newUser = await UserModel.create(userAdded);
    return newUser;
}
```
We will use this controller file to sign up to our application. Now back to our `sign-up.ts` file, we need to handle this authentification, open the file and paste the following line of code

```

import type { NextApiRequest, NextApiResponse } from 'next';
import { createUser } from '../controllers/user.controller';
type Data = {
  message: string
}

export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
    if (req.method==='POST') {
        const {email, password, ...otherProps} = req.body;
        if (!email || !email.includes('@') || !password) {
            res.status(422).json({ message: 'Invalid Data' });
            return;
        }
        const data = await createUser({email, password, ...otherProps})
        if (!data) {
            res.status(422).json({ message: 'User already exists' });
            return;
        }
        // sign in the user
        res.status(201).json({ message: 'User created',...data });
        return
    }else{
        res.status(500).send({message:'Invalid Route'})
    }
}

```
We import our user controller file, this is where we will write our API. We destructure the email and password then validate if the user exists, if not we create the user, we then hash the password. If user exists, we throw the error.

##Sign In <a name="chapter-4"></a>
Now we need to handle the sign in route for the user.

Next-Auth provides us with Client API as well as REST API
The **NextAuth.js client** library makes it easy to interact with sessions from React applications.

NextAuth.js exposes a REST API which is used by the NextAuth.js client. 
We will use both for signing in the users.

To add NextAuth.js to a project create a file called `[...nextauth].ts` in ``pages/api/auth.``

All requests to /api/auth/* (signin, callback, signout, etc) will automatically be handed by NextAuth.js.

Before we will need to handle the sign in the user, so we should update our controller file

```
export const getUserByEmail = async (email: string) => {
    const user: UserX | null = await UserModel.findOne({
        email
    });
    if(!user) throw new Error("No User Found");
    return user;
}
```
With this help from next-auth, we need to implement our own sign-in logic for checking users stored on the database.

In the `[...nextauth].ts` file, paste in the following lines of code. I will walk you write through it.

```
import NextAuth, {NextAuthOptions, Session, User} from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { getUserByEmail } from '../controllers/user.controller';
import { IUser } from '../users';
import bcrypt from 'bcryptjs';
import { NextApiHandler } from 'next';
import { getDoctorByStaffID } from '../controllers/doctor.controller';
interface Credentials{
  email: string;
  password: string;
}
export interface MySession extends Session{
    user:{
        id: string,
        name: string,
        email: string,
        phone: string,
        isAdmin: boolean,
        image: string
    }
}
interface MyUser extends User{
    id: string,
    name: string,
    email: string,
    phone: string,
    isAdmin: boolean,
}
export const options: NextAuthOptions = {
    session:{
        strategy: 'jwt',
    },
    callbacks: {
        async jwt({token, user}) {
            if (user) {
                token.user = user;
            }
            return token;
        },
        async session({ session,token }){
            if (token) {
                session.user = token.user as MyUser;
            }
            return Promise.resolve(session);
        },
    },
    secret: process.env.MONGODB_URI,
    providers: [
        Credentials({
            id: 'credentials',
            name: 'credentials',
            credentials:{
                email: {label:'email', type:'text',placeholder:''},
                password:{label:'password',type:'password'}
            },
            async authorize(credentials){
                const { email, password } = credentials as Credentials;
                const user: IUser| null = await getUserByEmail(email);
                if (!user) {
                    throw new Error("No User Found");
                }
                const isMatch = await bcrypt.compare(password, user.password);
                if (!isMatch) {
                    throw new Error("Password doesnt Match");
                }
                return {
                    id: user._id,
                    name: user.name,
                    email: user.email,
                };
            },
        })
    ],
}
const Handler: NextApiHandler = (req, res) => NextAuth(req, res, options);
export default Handler;
```
This code is compatible with NextAuth.js version 4. Here, we import the Credentials provider, this allows us to define a sign in with password and email.  Inside the ``authorize()`` function we pass in the email and password we had defined as input. we then return the user information. In the callback functions, we have the `session()` and `jwt()` function. this takes in the user we had returned in the `authorize()` so we can define all this. We used type assertions for defining its return type.

In the next session, we will continue with the client side.