Authentication in Web Applications
Tags: javascript, node
Published: 2020-12-04 05:24:55

Hello Everyone,
In this post, I am going to show you how authentication and login persistance is implemented in web applications using mysql and next.js.I am gonna assume you have some basic knowledge of node and react.

#Installation and Folder Structure
Open your favourite code editor and the the following code in the command line:
```javascript
npx create-next-app hello-next
```
This will creatte your next application with the name of hello-next.

![folder structure](https://dev-to-uploads.s3.amazonaws.com/i/q7lntnybeg5xilwk6vqn.PNG)

Your folder structure might look different. I recommend you should delete the files and create new files and folder as I have created in the picture.

Next, first we will setup our database which is mysql.

#MySQL
If you are following my folder structure then you should have created models folder and in it user.js file.

I have also installed mysql locally in my computer and you can install from this [link.](https://www.mysql.com/)
However, you can also setup the cloud for it. The choice is yours.

Now, we are going to create a `Schema` for our database.
For those of you, who don't know what `Schema` is, it is structure of our tables in the database about how and which data is going to be stored in the database.`Schema` is pretty everywhere, it is almost used with every database.

For our mysql database we are going to use `sequelize` for creating a schema.
```javascript
npm install --save sequelize
```
First, we are going to connect sequelize with the database. In the hello-next folder, create a folder called utils and in it create a file called dbConnect.js.
```javascript
import { Sequelize } from "sequelize";

module.exports = new Sequelize('signup_test', 'root', 'mysql_root_password', {
    host: 'localhost',
    dialect: "mysql"
});
```
Make to remove `mysql_root_password` with your root password.

Now, lets move to user.js file. Remember this file is created in the models folder.
```javascript
const crypto = require("crypto");
const { v4: uuidv1 } = require("uuid")
const { DataTypes } = require("sequelize")

const db = require("../utils/dbConnect")

const User = db.define("User", {
    fullname: {
        type: DataTypes.STRING,
        allowNull: false
    },
    email: {
        type: DataTypes.STRING,
        allowNull: false,
        unique: true
    },
    salt: {
        type: DataTypes.STRING,
        get(){
            return () => this.getDataValue('salt')
        }
    },
    password: {
        type: DataTypes.STRING,
        get(){
            return () =>  this.getDataValue('password')
        }
    }
},{
    tableName: "Users"
})

User.generateSalt = function() {
    return uuidv1()
}

User.encryptPassword = function(plainPassword, salt) {
    return crypto.createHmac('sha256', salt).update(plainPassword).digest("hex");
}

const setSaltAndPassword = user => {
    if(user.changed('password')) {
        user.salt = User.generateSalt();
        user.password = User.encryptPassword(user.password(), user.salt())
    }
}
User.prototype.correctPassword = function(enteredPassword) {
    return User.encryptPassword(enteredPassword, this.salt()) === this.password()
}


User.beforeCreate(setSaltAndPassword);
User.beforeUpdate(setSaltAndPassword);

module.exports = User
```
You might been seeing an error,we haven't installed the uuid.
```javascript
npm install uuid
```
okay, I am gonna walk you real quick about what we are doing in the above file.
We are gonna ask user for the three input values for the signup i.e, fullname, email and password.Now, you might be wondering what is this `salt` doing in the schema.

####Salt
Salt is a value that is added to your cryptography function to make the password of the user encrypted and only salt can decode the password.The point of using salt is that even though we encrypt our password but there are some hackers that can decrypt the password by reverse engineering methods. So, if we add a salt of our choice, it nearly become impossible to crack the password.

Okay, now lets move on
`User.generateSalt` function is created so that it can generate a unique string of numbers every time when a new user is signuped.

```javascript
User.encryptPassword = function(plainPassword, salt) {
    return crypto.createHmac('sha256', salt).update(plainPassword).digest("hex");
}
```
From the naming it is clear that this function receives the plain password and salt encrpt the password.If you want to learn more it, visit this [link.](https://nodejs.org/api/crypto.html#crypto_crypto)

```javascript
const setSaltAndPassword = user => {
    if(user.changed('password')) {
        user.salt = User.generateSalt();
        user.password = User.encryptPassword(user.password(), user.salt())
    }
}
```
And now, our final piece of encrpting the password is the `setSaltAndPassword` which takes the password that the user passed and salt that we generated to encrypt the password.

Remember, we are able to get password using this `user.password()` method because in our password field we added this `get()` line.
```javascript
get(){
            return () =>  this.getDataValue('password')
        }
```
And the same case goes for the salt also.

Also, for all these functions to take place, we use these methods also. Before, creating any new user data will be passed through them.
```javascript
User.beforeCreate(setSaltAndPassword);
User.beforeUpdate(setSaltAndPassword);
```

And finally to check if the password is correct or not, we use `correctPassword` function. This function encrypts the password passed by the user to check against the encrypt password stored in the database.
By doing this, we will never know about the original password.

#API - signup.js, login.js, signout.js and db.js

Remember inside the api, you have to create these files.
Lets, first deal with signup.js file.
```javascript
const User = require("../../models/user")

export default  (req, res) => {
    User.create(req.body)
        .then(data => {
            return res.json(data)
        })
        .catch(err => {
            if(err){
                return res.status(400).json({
                    error: "Not able to save user in db"
                })
            }
        })
}
```
Whenever user hits `/api/signup/` with post method then new user will be created.
okay, This was simple.

Login.js
```javascript
const User = require("../../models/user")

const jwt = require("jsonwebtoken")

export default (req, res) => {
    const { email, password } = req.body;


    User.findOne({ where: { email }})
        .then(user => {
            if(!user.correctPassword(password)){
                return res.status(401).json({
                    error: "Email and Password is not correct"
                })
            }

            //create token
            const token = jwt.sign({id: user.id}, process.env.SECRET)

            //send response to front
            const { id, fullname, email } = user;
            return res.status(200).json({
                token, user: { id, fullname, email }
            })

        })
        .catch(err => {
            if(err){
                return res.status(400).json({
                    error: "User email doesn't exist",
                    log: err
                })
            }
        })
}
```
First things first, `npm install jsonwebtoken` run this command in your cli.

####JsonWebToken (jwt)
Jwt generate a one time unique token generated which will help us to login the user.It maintains the session persistance and we are going to store this token in the cookie so that if a user refreshes the page, he/she is still logged in.
To learn more about the JWT, visit this [link.](https://jwt.io/)

We are using `findOne` method of sequelize which find the user based on his email.Remember, in our schema we set the `unique: true` value for the email so that their will be no duplicates of the email.
Next, we are checking if the password passed by the user is correct or not with `correctPassword` function.
Next, we are generating a token for the user and sending the user's credentials and token in the frontend.


signout.js
```javascript
export default (req, res) => {
    res.json({
        message: "remove token and user is signed out"
    })
}
```
Signout is simple, I am just send the message for now and but in the front end I will remove the cookie so that the user is not signed in.


db.js
```javascript
import db from "../../utils/dbConnect"


export default function(req, res) {
    db.authenticate()
    .then(res => console.log("DB CONNECTED"))
    .catch(err => console.log("ERROR IN CONNECTING DB"))
    res.json({
        message: "db is connected"
    })
}
```
This file is to connect database with our application.

#signup.js
Okay, moving to frontend, create a signup.js in pages folder.
```javascript
import React,{ useState } from 'react'
import { useRouter } from "next/router"

export default function signup() {
    const [signup, setSignup] = useState({
        fullname: "",
        email: "",
        password: ""
    })

    const [message, setMessage ] = useState("");
    const router = useRouter();

    const { fullname, email, password } = signup;

    const handleChange = (event) => {
        const { name, value } = event.target;
        setSignup({ ...signup, [name]: value })
    }

    const handleSubmit = (event) => {
        event.preventDefault();

        const data = { fullname, email, password };

        fetch("/api/signup",{
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify(data)
        }).then(res => res.json())
          .then(data => {
              if(!data.error){
                  setMessage("Please Login, You are signuped for the app")
              }
          })
          .catch(err => console.log(err))
        
        setSignup({
            fullname: "",
            email: "",
            password: ""
        })  

        router.push("/login")
    }

    return (
        <form onSubmit={handleSubmit}>
            {message}
            <input type="text" name="fullname" value={fullname} onChange={handleChange} placeholder="Full Name" />
            <br/> <br/>
            <input type="email" name="email" value={email} onChange={handleChange} placeholder='Email' />
            <br/> <br/>
            <input type="password" name="password" value={password} onChange={handleChange} placeholder="Password" />
            <br/> <br/>
            <input type="submit" value="Signup"/>
        </form>
    )
}
```
In this component, I am taking the fullname, email and password from the user and then on form submit save data to the `/api/signup/` route.

#Context
I have setup context in this as it turns out that you cannot do props drilling in next.js.
In the context folder, create a mainContext.js.
```javascript
import React,{ createContext, useState } from 'react';

export const MainContext = createContext();

export default function MainContextProvider(props) {

    const [authenticated, setAuthenticated] = useState(null);

    return (
        <MainContext.Provider value={{ authenticated, setAuthenticated }}>
            {props.children}
        </MainContext.Provider>
    )
}

```
Now, in the _app.js, change the code.
```javascript
import MainContextProvider from "../context/mainContext"
import { CookiesProvider } from "react-cookie"

export default function App({ Component, pageProps }){
    return (
        <MainContextProvider>
            <CookiesProvider>
                <Component {...pageProps} />
            </CookiesProvider>
        </MainContextProvider>
    )
}
```

As you can see we are using `react-cookie`, which will help us to access the cookie in the application.
So, in the cli, enter `npm install react-cookie`.

#Login.js
Now, create Login.js file in pages folder.
```javascript
import React,{ useContext, useState } from 'react'
import { useCookies } from "react-cookie"
import { useRouter } from "next/router"
import { MainContext } from '../context/mainContext';

export default function login() {
    const [login, setLogin] = useState({
        email: "",
        password: ""
    });
    const [cookie, setCookie] = useCookies(["token"]);
    const { setAuthenticated } = useContext(MainContext)
    const { email, password } = login;

    const router = useRouter();

    const handleChange = (event) => {
        const { name, value } = event.target;
        setLogin({ ...login, [name]: value })
    }

    const handleSubmit = (event) => {
        event.preventDefault();

        const data = { email, password };

        fetch("/api/login",{
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify(data)
        })
        .then(response => response.json()).then(data => {
            if(data.token){
                setCookie("token", `Bearer ${data.token}`, {
                    path: "/",
                    maxAge: 3600, //expires in 1 hour
                    sameSite: true
                })
                if(typeof window !== undefined){
                    localStorage.setItem("jwt", JSON.stringify(data));
                    setAuthenticated(data)
                }
            }
        })
        .catch(err => console.log(err))

        setLogin({
            email: "",
            password: ""
        })
        router.push("/")
    }

    return (
        <form onSubmit={handleSubmit}>
            <input type="email" name="email" value={email} onChange={handleChange} placeholder="Email" />
            <br/> <br/>
            <input type="password" name="password" value={password} onChange={handleChange} placeholder="Password" />
            <br/> <br/>
            <input type="submit" value="Login"/>
        </form>
    )
}

```

In this component, we are using `react-cookie` which gives us the use cookies hooks in which returns three things.
1. cookie:- The cookie which is curently stored in your browser.
2. setCookie:- To store a new cookie in the browser.
3. removeCookie:- To remove the cookie from the browser.

Although, in this component, we are using the first two only but in the index.js you will see the example of `removeCookie`.

Furthermore, I am taking the email and password as a input from the user and on form submit, checking the email and password with `/api/login` route.
In the success response from it, I got the token and user credentials in which I store the token using the setCookie which take some options like `maxAge` which describes when the cookie is gonna expires which in this case is 1 hour.

Next, I am also storing the data in the localStorage and in the `autenticated` state.

Finally, when the success is done, router is push backed to "/".

#Index.js

This is the main component on which the application mounts on.
```javascript
import { useEffect, Fragment, useContext } from "react"
import { useCookies } from "react-cookie"

import Link from "next/link"
import { MainContext } from "../context/mainContext";

export default function Home() {

  const { authenticated, setAuthenticated } = useContext(MainContext);

  const [cookie, setCookie, removeCookie] = useCookies(["token"]);


  useEffect(() => {
    dbCall()
  }, [])

  useEffect(() => {
    if(!authenticated){ 
      setAuthenticated(isAuthenticated())
    }
  },[authenticated])

  const dbCall = async () => {
    const response = await fetch("/api/db")
    const data = await response.json();
    console.log(data)
  }

  const handleSignout = () => {
    fetch("/api/signout").then(response => response.json())
    .then(data => {
      if(data.message === "remove token and user is signed out"){
        removeCookie("token")
        setAuthenticated(null)
      }
    })
    .catch(err => console.log(err))
  }

  const isAuthenticated = () => {
    if(typeof window !== undefined){
      if(localStorage.getItem("jwt") && cookie.token){
        return JSON.parse(localStorage.getItem("jwt"));
        
      }else{
        return false
      }
    }
  }

  return (
    <ul>
      <li><Link href="/">Home</Link></li>
      {
        authenticated && authenticated.user ? (
          <li onClick={handleSignout}>Signout</li>
        ) : (
          <Fragment>
            <li><Link href="/login">Login</Link></li>
            <li><Link href="/signup">SignUp</Link></li>
          </Fragment>
        )
      }
    </ul>
  )
}
```
In this component, there are two main functions `isAuthenticated` and `handleSignout`.

isAuthenticated() :- This function is invoked when the app is mounted for the first and it checks if the `jwt` key for the localStorage and cookie token is presented in it and then it will authenticate the user and `setAuthenticated` state is updated.Based on this, login, signup and signout is gonna be the present to the user.

handleSignout() :- When the user clicked on signout, we remove the token cookie and setAuthenticated state to null.

Also, in this component, we are connecting our database with dbCall function.

#Conclusion
Now, you have the authenticated application with user persistance session.

If you think I might have mentioned something wrong, please feel free to comment. We all are learning here.

Thanks for your time to read this.
Happy Coding:)