Login & Signup page with Auth - NextJS & MongoDB
Updated at: 27 May 2023Deepak Painkra
Today, we are making an authentication system for login and sign-up pages, which includes CryptoJS for password hashing and JWT for Authentication.
First, open up your terminal and run this command
No for every option because we are not using anything such as typescript, src, app router, and tailwind.
Afterwards, we must install these necessary packages, run this command for installation of this packages
This package is more useful when making connections between clients and MongoDB.
For Password encryption (encryption & decryption) so we run these command
Another packages we need to install is JWT, and we run this command to install this packages
The reason we are implementing the JWT in our project is that it will save tokens in the local storage, it will authenticate whenever the person takes any action, such as updating the profile, and you can track users whenever they go online to see their user profile.
Environment Variable Setup
The installation process has finished. So we need to set up an environment variable. Create a .env.local file in the root directory of the project, and then add these variables
Note:-
You must have installed MongoDB compass in your system, and if you have not installed it yet, after that, go to the official MongoDB website and download the community version, and install it as a service only if you have more than 8 GB of ram .
Making a Connection between the MongoDB and Frontend
First, create a folder name middleware in the root directory of the project, then create a mongoose.js file inside the middleware folder, and the path name will be middleware/mongoose.js
After that, write this code inside the mongoose.js file
import mongoose from "mongoose";
const connectDb = handler => async (req, res)=>{
if(mongoose.connections[0].readystate){
return handler(req,res)
}
await mongoose.connect(process.env.MONGO_URI)
return handler(req,res);
}
export default connectDb;
It says that if the connection is in the ready state, then return (req, res), but this will not be the case, so we need to make a connection, so await and then make a connection.
Schema for model
Next steps, create a model folder in the root directory, then create a file inside the model folder User.js, and the path name will be model/User.js
Now write these codes inside the User.js file
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {type: String, required: true},
email: {type: String, required: true, unique: true},
password: {type: String, required: true},
//rePassword: {type: String, required: true},
},{timestamps: true});
// mongoose.models= {}
// export default mongoose.model('User', UserSchema);
export default mongoose.models.User || mongoose.model('User', UserSchema);
So this is how you can create a schema, and we have added timestamps true because it will add (created at and updated at.)
It says that if there is no User collection, it will create a new collection with the name of User.
Creating an API for the registration page
First, create a signup.js file inside the pages/ap i directory, and then we are going to write these codes
Warning:- Ensure that you have replaced that jwtSecret and secret1234 with your secret key, and ensure that its length should be more than 32 characters.
import User from "@/models/User";
import connectDb from "@/middleware/mongoose";
var CryptoJS = require("crypto-js");
const handler = async (req, res) => {
try {
//await connectDb();
if (req.method == "POST") {
const {name, email} = req.body;
const u = new User({name, email, password: CryptoJS.AES.encrypt(req.body.password, 'secret1234').toString()});
await u.save();
res.status(200).json({ success: "success!" });
} else {
res.status(400).json({ error: "error! This method is not allowed." });
}
} catch (error) {
console.error(error);
res.status(500).json({ error: "Server Error" });
}
};
export default connectDb(handler);
First, we will wrap up with the try and catch statement, and if the (req.method) is POST, then await and connect to the DB,
First, we will get the name and email from the req.body, and then Next, we will create a variable u and then convert it into the object, but we will implement the crypto-js for encryption because we are not storing passwords in the form of plain text. After that, we will return the req.status(200) for success and in case we get any errors, it will return the req.status(400), which is a bad request.
Creating an API for the login page
Create a login.js file inside the pages/api directory, then write these codes inside the login.js file
import User from "@/models/User";
import connectDb from "@/middleware/mongoose";
var CryptoJS = require("crypto-js");
var jwt = require('jsonwebtoken');
const handler = async (req, res) => {
try {
if (req.method == "POST") {
let user = await User.findOne({ "email": req.body.email })
const bytes = CryptoJS.AES.decrypt(user.password, 'secret1234');
let decryptedPass = bytes.toString(CryptoJS.enc.Utf8);
if (user) {
if (req.body.email == user.email && req.body.password == decryptedPass) {
var token = jwt.sign({email: user.email, name: user.name}, 'jwtSecret', {expiresIn:"1d"});
res.status(200).json({success: true, token});
}
else{
res.status(200).json({ success: false, error: "Invalid email or password." });
}
}
else {
res.status(200).json({ success: false, error: "No User Found!" });
}
} else {
res.status(400).json({ error: "error! This method is not allowed." });
}
} catch (error) {
console.error(error);
res.status(500).json({ error: "Server Error" });
}
};
export default connectDb(handler);
And it says that if the req.method is POST, then we will create a variable user, then we have to write findOne to find a user, and if the email already exists, we will get the response back,
If we get a response back, then we will decrypt the password and also verify the jsonwebtokon, then after we let the users log in
Frontend Part
I am hoping that you may already know how the form submission works in NextJS, and if not, then we are using useState to set value and change the value.
import React from 'react'
import styles from '../styles/Home.module.css';
import Link from 'next/link'
import { useState } from 'react'
import Router from 'next/router';
const signup = () => {
const [name, setName] = useState()
const [email, setEmail] = useState()
const [password, setPassword] = useState()
const handleChange = (e) => {
if (e.target.name == 'name') {
setName(e.target.value)
}
else if (e.target.name == 'email') {
setEmail(e.target.value)
}
else if (e.target.name == 'password') {
setPassword(e.target.value)
}
}
const handleSumbit = async (e) => {
e.preventDefault()
const data = { name, email, password }
let res = await fetch('api/signup', {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
let response = await res.json()
console.log(response)
setEmail('')
setName('')
setPassword('')
setTimeout(()=>{
Router.push('http://localhost:3000/login')
}, 1000);
}
return (
<div className={styles.container}>
<form className={styles.beautifulForm} onSubmit={handleSumbit}>
<div className={styles.formGroup}>
<label className={styles.holder} value={name} htmlFor="name">Name</label>
<input className={styles.feild} onChange={handleChange} type="text" id="name" name="name" required />
</div>
<div className={styles.formGroup}>
<label className={styles.holder} value={email} htmlFor="email">Email</label>
<input className={styles.feild} onChange={handleChange} type="email" id="email" name="email" required />
</div>
<div className={styles.formGroup}>
<label className={styles.holder} htmlFor="password">password</label>
<input className={styles.area} type="password" value={password} onChange={handleChange} id="password" name="password" required />
</div>
<button className={styles.btn} type="submit">Submit</button>
</form>
</div>
);
}
export default signup
Afterwards, we are going to define two functions, the first one is handleChange, and the second one is handleSumbit. We are using handleChange for changing the value and handleSumbit for the form submission,
This form will work for both, just by removing the name section and changing the api key, in case we will use login functionality.
import React, { useEffect } from 'react'
import styles from '../styles/Home.module.css';
import Link from 'next/link'
import { useState } from 'react'
import Router, { useRouter } from 'next/router';
const login = () => {
const router = useRouter()
const [email, setEmail] = useState()
const [password, setPassword] = useState()
const handleChange = (e) => {
if (e.target.name == 'email') {
setEmail(e.target.value)
}
else if (e.target.name == 'password') {
setPassword(e.target.value)
}
}
const handleSumbit = async (e) => {
e.preventDefault()
const data = {email, password}
let res = await fetch('api/login', {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
let response = await res.json()
console.log(response)
setEmail('')
setPassword('')
if(response.success){
localStorage.setItem('token', response.token)
setTimeout(()=>{
Router.push('http://localhost:3000')
}, 1000);
}
}
return (
<div className={styles.container}>
<form className={styles.beautifulForm} onSubmit={handleSumbit}>
<div className={styles.formGroup}>
<label className={styles.holder} value={email} htmlFor="email">Email</label>
<input className={styles.feild} onChange={handleChange} type="email" id="email" name="email" required />
</div>
<div className={styles.formGroup}>
<label className={styles.holder} htmlFor="password">password</label>
<input className={styles.area} type="password" value={password} onChange={handleChange} id="password" name="password" required/>
</div>
<button className={styles.btn} type="submit">login</button>
</form>
</div>
)
}
export default login
In the end, if got a response back successfully, then useState will set the name, email, and password to the previous state,
If the response is successful, we will set the token to the localStorage, which we will use to trigger the login and logout functionality.
CSS Part
As I am using the NextJS module CSS component, add these styling to the file that has mentioned
.container{
padding: 10rem;
}
.beautifulForm {
max-width: 400px;
margin: 0 auto;
padding: 20px;
background-color: #c8d2d5;
border-radius: 10px;
}
.feild{
width: 100%;
height: 5vh;
}
.formGroup {
margin-bottom: 20px;
}
.holder {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.area {
width: 100%;
padding: 10px;
border-radius: 5px;
border: 1px solid #cccccc;
}
.btn {
display: block;
width: 100%;
padding: 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.btn:hover {
background-color: #45a049;
}
Logout Page
You can log out just by removing the token from the local storage, and also go to the browser and delete the jwt token , and it will log out of your page. You can also create a separate button to achieve this functionality.