Setting up Auth Routes with Express
Tags: webdev, security, node, javascript
Published: 2020-08-15 18:02:59

In the previous part , we learnt how to connect to MongoDB with Mongoose. We also defined our basic User Schema.

In this part , we're going to setup our `/login` and our `/registration` routes. These API endpoints are going to allow our client-side applications to send `POST` requests to register new users as well as to allow existing users to login to their account.

## Setting up our Routes ✨

Let's start by creating a new folder in our project directory called `routes`.

In this folder , we're going to create our `Auth.route.js` file. We're going to define all our Routes in this file and later import it in our `server.js` file.

```Javascript
const express = require('express');
const router = express.Router();

const User = require('../models/User.model');

router.post('/register',async(req,res) => {
  try {
   res.json({message:"This is the register route!"})
  }
  catch(err) {
   console.error(err.message)
  }
})

router.post('/login',async(req,res) => {
  try {
   res.json({message:"This is the login route!"})
  }
  catch(err) {
   console.error(err.message)
  }
})

```

In the code above , we `require('express')` and then instantiate `Router` which is built into Express. 

Router helps us build out our routes. We can handle `get` , `post` , `patch` , `delete` and most other **HTTP** request methods for our routes by chaining the required type of request to our `router` object. i.e

```Javascript
router.get('/[our-required-route]',() => {
  //This is a callback function
})
```
We're going to define what should be done when the route is hit, inside the callback function.

In our first code snippet , our callback function is `async`. This will make our life easier later when we need to interface with MongoDB to fetch and post data.

For the sake of testing , we will send back json data , with a message key containing a `string value` using `res.json`.

The callback functions come with a `req` and `res` parameter which help us interact with a user's request and the response we can send back respectively.

Let's finally add this route to our `server.js` file. Add the following code before the `require('./helpers/initDB')()` line.

We will require our `Auth.route` file and initialise it to `AuthRoute`. We will finally use the `AuthRoute` by making use of the `use` method that Express provides. We'll also define the `parent` route to be `/api/auth`. This means that if we want to hit our `register` route , we'll actually have to hit `/api/auth/register`.
```Javascript
...

const AuthRoute = require('./routes/Auth.route');
app.use('/api/auth', AuthRoute);

...
```


## Installing REST Client on VSCode to test our APIs ☀️

If you want to test the APIs we've just built , you can download the REST Client by going to the Extensions tab on VSCode. You can additionally download Postman or Insomnia as well to test your API.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4fdb9yilt79rk33y7mu1.png)



Let's make a `POST` request to our APIs that we defined earlier. 
In our `routes` folder, create a new file called `route.http`. Then write the following line into this file.

```HTTP
POST https://localhost:5000/api/auth/register
```

You'll see a `Send Request` label pop up just over this line now. Click on it.

This will now open a tab on the side with a JSON response.
This response should be

```JSON
"message" : "This is the register route!"
```

Make sure your server is running before making the request. You can do this by using `npm start`.

## Analysing our Login/Registration workflows

Before we can Login or Register users , we need to break down what we need to do step-by-step.

#### Let's look at our Registration workflow.

1. Validate the received registration details
2. If there is an error in received registration details , then return `400` status code and the error message.
3. Check if email exists. (`400` if error)
4. Check is username exists.  (`400` if error)
5. Salt and then hash the password. (Read Part One)
6. Save our user in the database. 

#### Let's break down our Login Workflow next.

1. Validate the received login details. 
2. Check if user with given email exists. (400 if error)
3. Check the received user password against the hashed DB password using `bcrypt.compare()`.
4. Return Success message if password matches , else return invalid details message. (Additionally provide a JWT Token which we will discuss in part 4)

In both the workflows described above , we need to validate the details that we receive from the client side. This involves a lot of string handling which can be tedious work.
However , in this tutorial we're going to use a ready-made package for validation called `Joi`.

We're also going to install another package called `bcrpyt`. **Bcrypt** provides ways to salt , hash and compare passwords with its built in methods.

Let's install them both. Exit your server using `Ctrl+C` or `Cmd+C` and run the following npm command.

```Bash
npm install @hapi/joi bcrpyt
```

## Writing our Joi validation schemas 🌈

Let's get started on writing our Joi validation schemas. Writing a Joi Validation schema is very easy. We define a `Joi` object and define the requirements that our data in this Joi object should have. We can do this by chaining together the built-in methods that Joi provides. 

Want to check if a string has at least 6 characters and can only be alphanumerical? 

We can achieve this simply with the following code

```Javascript
ourString: Joi.string().min(6).alphanum(),
```

Joi will return an error message if the `ourString` value does not pass the conditions.

Let's now go ahead and build out our validation schemas for the auth-api.

Create a `validator.js` file in your `/helpers` directory.
Add the following code to this file.

```Javascript
const Joi = require('@hapi/joi');

const registrationValidator = (data) => {
    const schema = Joi.object({
        username: Joi.string().min(6).required().alphanum(),
        email: Joi.string().min(6).required().email(),
        password: Joi.string().min(6).required(),
        role: Joi.string()
    })
    return schema.validate(data);
}

const loginValidator = (data) => {
    const schema = Joi.object({
        email: Joi.string().min(6).required(),
        password: Joi.string().min(6).required()
    })
    return schema.validate(data);
}

module.exports.registrationValidator = registrationValidator;
module.exports.loginValidator = loginValidator;

```

Finally let's require this file in our `Auth.route.js` file.

```Javascript
const { registrationValidator, loginValidator } = require('../helpers/validator');
``` 

## Building our Register Route 🌈

Inside our `try` block , lets start by processing the data that we receive by using `req.body`. 


```Javascript
try {
const { error } = registrationValidator(req.body);
if (error) {
return res.status(400).send(error.details[0].message);
}
}

```

We pass `req.body` to our `registrationValidator` function that we previously defined our `validator.js` file.

If our validator encounters an error in the receiver data , we're going to return the error message with a status code of `400`.

You can test if the API works so far by going to the `rest.http` and adding the following

```HTTP
POST https://localhost:5000/api/auth/register
content-type: application/json
{
	"email":"test@test.com",
	"username":"test",
	"password":"test",
}
```

After hitting the `Send Request` button , you'll see that we get an error message with a `400` status code. This is because both our `username` and `password` are only 4 letters long.

Now that validation is done , We can check if the username or email already exist in the database. 

**Checking if username and email already exist**

Add the following code next ,

```Javascript
//EmailExistCheck
const emailExists = await User.exists({ email: req.body.email });
if (emailExists) return res.status(400).send('Email already exists.');


//UsernameExistCheck
const userNameExists = await User.exists({ username: req.body.username });
if (userNameExists) return res.status(400).send('Username already exists.');
```

We use the `exists` method that MongoDB provides to check if a document containing the given data exists.

We'll return the error message with a `400` status code if either values exist.

**Salting and hashing our passwords before storing**

Let's make use of the `bcrypt` library that we had installed earlier. Make sure you've imported the bcrypt library with the following code.

```Javascript
const bcrypt = require('bcrypt');
```

Next , let's generate a `salt` using the in-built `genSalt()` method inside bcrypt.

```Javascript
   const salt = await bcrypt.genSalt(10);
```
 
If you're unaware about salting or hashing , read the [first article of this series](https://dev.to/aritik/writing-a-basic-authentication-api-with-express-and-mongodb-1-j8).

The bcrypt `genSalt()` method generates a salt for us that we'll now use with our password. Let's use the `bcrypt.hash()` method to hash our salted password. This method takes the base password and the generated salt as its parameters.

Go ahead and add the following code to your file next.

```Javascript
 const hashPassword = await bcrypt.hash(req.body.password, salt);
```

Now that we have hashed our password , let's go ahead and construct our new user object with the newly hashed password.

```Javascript
     const user = new User({
                username: req.body.username,
                email: req.body.email,
                password: hashPassword,
            });

```

Finally , let's save this user into our Database using the `save()` method.

```Javascript
   const savedUser = await user.save();
            res.send(savedUser);
```

Let's send back the user that we have saved just now as our response with the `res.send()` method.

Finally , go back to the `rest.http` file and make a `POST` request with three valid user credentials defined by our Joi Schema.

If everything went well , you should see the saved user's details containing the hashed password in your response.

You can additionally also go to your Mongo Atlas Client to see if the user's details were registered.

With this , we've finished the process of registering our user.

Let's move on to building out the `/login` route next.

## Building our Login route 🌈

Building the login system involves the same validation process as registering our users. Go ahead and paste the following code in your file inside the `try` block of your `login` route.

We're also going to use MongoDB's `findOne()` method to extract the credentials of the corresponding email that the user had entered. We will store this inside a `user` variable.

```Javascript
    //Use Login Values Validator
            const { error } = loginValidator(req.body);
            if (error) return res.status(400).send(error.details[0].message)

            //UserExistCheck
            const user = await User.findOne({ email: req.body.email });
            if (!user) return res.status(400).send('Account does not exist with provided email and password combination.');
```

**Comparing the hashed password with the entered password**

To compare our passwords, we're going to make use of bcrypt's `.compare()` method. This method takes the user's entered password as its first parameter and the hashed password stored in the DB that we extracted earlier.

```Javascript
const validPassword = await bcrypt.compare(req.body.password, user.password);
            if (!validPassword) return res.status(400).send('Incorrect Password');
```

The code above stores the `bool` result of the `bcrypt.compare()` method. If the password is invalid , we return a message "Incorrect Password" with a status code of `400`.

Finally , we'll return a `success` message back to the user to simulate a successful login attempt using the `res.send()` method.

```Javascript
res.send("Login Successful!")
```

Finally . you can test this out in your `rest.http` file by making a `POST` request to `/api/auth/login` with valid credentials. If all goes well , you should now see the "Login Successful" message!

**Congratulations! 🎉**

You've just built a login/registration system using Express and MongoDB. 

In the next part , we're going to deal with *JWTifying* our **Authentication/Authorisation** process. 👨🏻‍💻