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
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
, I will also be using the src
directory with NextJS, but fill free to use it to your own liking.
Installing Dependencies
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
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
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.
Top comments (0)