January 06, 2020

Building Authentication API using expressjs and JWT

In this tutorial , we will create a REST API from scratch using Nodejs and express , validate the data submitted and secure the endpoints via JSON Web Token (JWT) . We will also use MongoDB / Mongo Atlas to store our data .

The code for this tutorial can be found on github , see below ;-

https://github.com/robertrutenge/express-auth-api.git

Without further due , let’s get started as we have a lot of code to write . First thing first , you need to have node installed on your machine , create a project folder and open terminal then inside your project folder let’s fire up our app by initializing our node project .

npm init -y

Note : We have use -y to accept all default options

Let’s install expressjs

npm install express

Now , let’s create index.js file on a root of our project as its our main entry point . Inside index.js file we will create a basic express app as below; —

const express = require("express");

const app = express();
const port = 3000;

app.listen(port, () => {
  console.log(`Auth APIs listening on port ${port}`);
});

Now let’s proceed , and run our app

node index.js

You should see a message Auth APIs listening on port 3000 on your console log . If you try to access the url , localhost:3000 now you should get Cannot GET / as no route has been created yet .

Let’s add start script inside package.json file so as we don’t have to launch our app every time by typing node index.js . remove the test script and add start script as below ;-

{
  "name": "express-auth-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  }

}

To avoid restarting our app everytime we make changes to it , we are going to use nodemon so as the refresh happens automatically . go on install nodemon as a dev dependency .

npm i -D nodemon

Note: You can install nodemon globally if you want to so as you can use it in all your projects

Modify the start script to use nodemon

{
  "name": "express-auth-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon index.js"
  }

}

Now let’s start our app , we should be good to go .

npm start

Now let’s start building by creating an endpoint for registration . Let’s start with the basic implementation ;-

const express = require("express");

const app = express();
const port = 3000;

app.post("/register", (req, res) => {
  res.send("Register");
});

app.listen(port, () => {
  console.log(`Auth APIs listening on port ${port}`);
});

As you can see on line 6 above , we have implemented a simple route for registration . Go on open postman and test going to localhost:3000/register and you should see Register returned as a response . That means so far so good .

Now for simplicity , we will collect 3 fields during regisrtation , namely person’s full name , email and password .

On the register route , lets change it to return our data

res.send(req.body)

Now , Let’s make that call on postman like below ;

Register Postman Call 01

If we make a call we can see it does not return anything , if you try to console.log req.body you will find that it’s undefined . This is because we need to add body parser middleware to have access to our req.body , let’s go ahead and do that .

Above our register route function add below code;

app.use(express.json());

The above basically tells express to recognize the incoming request object as JSON Object . Now if we run again our register endpoint , we should be able to get response .

Register Postman Call 02

Now that we can get our response , let’s take some time to add some validation on the data submitted . As of now if you send empty data or remove one of the fields , it should work fine .

We are going to use a package called joi for validation . Go on and install it .

npm install @hapi/joi

Then we will import in our main file and add validation as below :-

const express = require("express");
const Joi = require("@hapi/joi");
const app = express();
const port = 3000;

app.use(express.json());

const registrationSchema = Joi.object({
  fullname: Joi.string().required(),
  email: Joi.string()
    .required()
    .email(),
  password: Joi.string()
    .min(6)
    .required()
});

app.post("/register", (req, res) => {
  const validation = registrationSchema.validate(req.body);
  res.send(validation);
});

app.listen(port, () => {
  console.log(`Auth APIs listening on port ${port}`);
});

As seen above , we have created a schema and add validation rules for our fields . If you now try for example to submit password less than 6 characters you should get an error or submit invalid email .

Since when we get an error , we are sending the whole object as below

{
    "value": {
        "fullname": "John Doe",
        "email": "johndoe@example.com",
        "password": "12345"
    },
    "error": {
        "_original": {
            "fullname": "John Doe",
            "email": "johndoe@example.com",
            "password": "12345"
        },
        "details": [
            {
                "message": "\"password\" length must be at least 6 characters long",
                "path": [
                    "password"
                ],
                "type": "string.min",
                "context": {
                    "limit": 6,
                    "value": "12345",
                    "label": "password",
                    "key": "password"
                }
            }
        ]
    }
}

We will use object destructuring to get the error object , and then return 400 with the appropriate message which is under details . change the register route to below ; —

app.post(“/register”, (req, res) => {
const { error } = registrationSchema.validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
res.send(“Registered!”);
});

If you run again , you will see 400 error with just a message , and if all validations have passed you will see “Registered!” message .

Now that we are done with validations , let’s go on and persist our data to the Database . As mentioned before , we are going to use MongoDB as our database , so go on to MongoDB Atlast , register or login .

Once login , click Build a new cluster on top right , choose a free tier with MO Sandbox which is free forever . Once everything is setup , go under the newly created cluster , and click connect , where you will have options on how to connect to your cluster as below ;-

Mongo Atlas Cluster

Choose Connect Your Application , in which you will presented with connection string . Keep that connection string as we will use it in our application . And that’s it for Mongo Atlas .

Now to connect to MongoDB in our application , we will need to install a package called mongoose .

npm install mongoose

Now import mongoose and establish a connection

const mongoose = require(“mongoose”);
mongoose.connect(“mongodb+srv://your_connection_string”,
{useNewUrlParser: true, useUnifiedTopology: true });

Once connected , let’s go and create our schema as below :-

const UserSchema = new mongoose.Schema({
fullname: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true }
});
const User = mongoose.model(“user”, UserSchema);

Once our schema is set , we use mongoose.model() function to create our model as shown above .

Once our model is ready , the last step is to persist the data sent via post , see the update register route below :-

app.post("/register", async (req, res) => {
  const { error } = registrationSchema.validate(req.body);
  if (error) return res.status(400).send(error.details[0].message);
  const user = new User({
    fullname: req.body.fullname,
    email: req.body.email,
    password: req.body.password
  });
  const savedUser = await user.save();
  res.send(savedUser);
});

On the above , we have created an instance of our User model and then use the save method to persist the data . Note we have changed our function to async as we need to wait for some DB operations to end like persisting the date .

If you go on . and run the register endpoint on postman , you should be able to get a response with our newly persisted user object with an extra _id field . Also if you go to your cluster , then collections , you should see a newly created users collection with one document that has the data submitted .

Another thing before we start to login , we can see that the password is saved as plain text , we can change this by using a package called bcryptjs which will allow us to encrypt our password . go on and install it .

npm i bcryptjs

Let’s change the register function , and encrypt our password .

app.post("/register", async (req, res) => {
  const { error } = registrationSchema.validate(req.body);
  if (error) return res.status(400).send(error.details[0].message);
  const salt = await bcrypt.genSalt(10);
  const hashedPassword = await bcrypt.hash(req.body.password, salt);
  const user = new User({
    fullname: req.body.fullname,
    email: req.body.email,
    password: hashedPassword
  });
  const savedUser = await user.save();
  res.send(savedUser);
});

On line 4 , we are using salting to create a secure password . Now if you call the register endpoint again , the password should be encrypted .

One last thing , to ensure best practice , we will also check if email exists on registering as well as try and catch the model.save() just incase anything went goes wrong .

const emailExists = await User.findOne({ email: req.body.email });
if (emailExists) return res.status(400).send(“Email already exists”);

Also

try {
const savedUser = await user.save();
res.send(savedUser);
} catch (error) {
res.status(400).send(error);
}

Our final register route should be as below :

app.post("/register", async (req, res) => {
  const { error } = registrationSchema.validate(req.body);
  if (error) return res.status(400).send(error.details[0].message);

  const emailExists = await User.findOne({ email: req.body.email });
  if (emailExists) return res.status(400).send("Email already exists");

  const salt = await bcrypt.genSalt(10);
  const hashedPassword = await bcrypt.hash(req.body.password, salt);

  const user = new User({
    fullname: req.body.fullname,
    email: req.body.email,
    password: hashedPassword
  });
  try {
    const savedUser = await user.save();
    res.send(savedUser);
  } catch (error) {
    res.status(400).send(error);
  }
});

Now , let’s go and continue with login . To avoid this tutorial being too long as it’s already is , go ahead and validate the login credentials as its the same process we did with register .

I went along and added the login code as below , it should be straight forward :-

app.post("/login", async (req, res) => {
  // TO-Do : Validate login

  // Check if user exists
  const user = await User.findOne({ email: req.body.email });
  if (!user) return res.status(400).send("User does not exists");

  //check if password is correct
  const passwordCheck = await bcrypt.compare(req.body.password, user.password);
  if (!passwordCheck) return res.status(400).send("Password not correct");

  res.send("Logged In");
});

Now if email and password is correct , you are going to see “Logged In” message . Note that for the purpose of this tutorial , the email and password not correct messages are different but ideally and for security purpose they should be the same like “Email or password incorrect” .

Great ! now let’s finalize the last part for securing our endpoints . for this we are going to use a package called jsonwebtoken , let’s install it .

npm i jsonwebtoken

As usual , we will import it .

const jwt = require(“jsonwebtoken”);

From here , we are going to create a token when a user logs in using jwt.sign() method which accepts a payload as a first parameter and a secret key which ideally you will store it in your environment variables .

const token = jwt.sign(
{ _id: user._id, email: user.email },
“YOUR_SECRET_KEY”
);
res.header(“auth_token”, token).send(token);

Now once logged in , we will add auth_token on response header so as it can be used to check authorization of various resources . Our Final login should look like below : —

app.post("/login", async (req, res) => {
  // TO-Do : Validate login

  // Check if user exists
  const user = await User.findOne({ email: req.body.email });
  if (!user) return res.status(400).send("User does not exists");

  //check if password is correct
  const passwordCheck = await bcrypt.compare(req.body.password, user.password);
  if (!passwordCheck) return res.status(400).send("Password not correct");

  //Create a token
  const token = jwt.sign(
    { _id: user._id, email: user.email },
    "OUR_VERY_SECURED_SECRET_KEY"
  );

  res.header("auth_token", token).send(token);
});

One last thing before we call it off . Now that we have generate a token we need to learn how to secure our private routes .

We will secure our routes by creating a middleware which will first check if auth_token is present in our request and if not will throw 401 , unauthorized error . It will then use jwt.verify() method to verify the token and serve our request if passed . see below :-

const tokenVerifier = (req, res, next) => {
  const token = req.header("auth_token");
  if (!token) return res.status(401).send("Access Denied");

  try {
    const verified = jwt.verify(token, "OUR_VERY_SECURED_SECRET_KEY");
    req.user = verified;
    next();
  } catch (error) {
    res.status(400).send("Invalid Token");
  }
};

Note . jwt.verify() will decrypt our token and give us a payload that we passed during the process of generating the token using jwt.sign() . If you have the token you can also decrypt it manually by going to https://jwt.io/ and paste your encoded token as below for my scenario .

JWT Decrypt

Now , let’s use our middleware . We will create a simple get endpoint , let’s call it dashboard and it will contain very secure data that a user needs to be authenticated to access it .

app.get(“/dashboard”, (req, res) => {
res.send(“VERY CONFIDENTIAL DATA”);
});

If you run this on postman now , you should get “VERY CONFIDENTIAL DATA” . Now let’s go on and secure it . Its as simple as adding our middleware function on our get route .

app.get(“/dashboard”, tokenVerifier, (req, res) => { res.send(“VERY CONFIDENTIAL DATA”); });

Now if you run it on postman , you should get access denied message !

Access Denied Postman

Go on postman and call login endpoint again , get the returned authtoken . Return back to dashboard endpoint , create authtoken header on postman with the generated token during login as its value and then call it again .

Confidential Data

You can now see “VERY CONFIDENTIAL DATA” .

That’s it folks . Hope this helps you in one way or the other , don’t hesitate to reach out incase of any comments .

Buy Me A Coffee

Robert Rutenge
Front-end Developer | ReactJS enthusiast | Problem Solver . Find me on twitter and Github