Categories
Codes Javascript Node.js

How to Create REST API Using Node.js – Part 4

“We live in a world that has walls and those walls need to be guarded by men with guns.”

― Aaron Sorkin, A Few Good Men

Introduction

Time sure flies. It’s already the fourth part of the “How to Create REST API Using Node.js”. If you are following these series until now and reading this post, thank you very much! It pleases me that I can do something useful for others.

Should you stumble upon this post first, you can find the previous posts here:

Alright then, now to the real intro for this post. On the last previously mentioned three posts, we learned about using Node.js to create a REST API application with Express. On the second part, We also learned about data operations using MongoDB and integrate it to the application. Then, the third part was about implementing cache with Redis to improve the performance of our REST API application.

But, our application is not secure because anyone can access it. We need to add some security to our system. Let’s start by securing our API routes by adding JWT. As usual, I will share the source code on GitHub at the end of this post.

Requirements

JWT Library

It’s simple this time, we just need to install jsonwebtoken to the project. On the terminal, execute npm i jsonwebtoken.

If you want to start this part fresh, please clone from this repository.

Part 4 – Improve API Routes Security by Implementing JWT

POST /login

First, we revisit our POST /login route and modify it to its real purpose after being neglected for 3 parts of tutorial.

index.js

Let’s modify POST /login from index.js. See this snippet below:

// index.js
...

app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  if (username && password) {
    const result = await mongoDbConnector.findOne(
      collection,
      { username, password }
    );

    if (result !== 'Data not found.') {
      const update = await mongoDbConnector.updateOne(
        collection,
        { username, password },
        { lastLogin: new Date(), logged: true }
      );
      res.send({ 
        status: 'success',
        message: `Logged as ${username}` 
      });
    }
    else {
      res.send({ 
        status: 'error',
        message: 'Wrong username or password!' 
      });
    }
  }
  else {
    res.send({ 
      status: 'error',
      message: 'Username or password field is missing!' 
    });
  }
});

...

We will modify app.post('/login'). The callback function is set to be asynchronous, because there will be some DB operations.

First, we fetch the username and password from req.body. If any of these fields are missing, the response from POST /login will be an error “Username or password field is missing!”.

Then, if username and password exists, it tries to find a document containing those credentials with mongoDbConnector.findOne(). If it is not found, it will return “Data not found.” which will result in POST /login route will return an error response “Wrong username and password!” from res.send().

If mongoDbConnector.findOne()has a result, it will pass through the if logic and call mongoDbConnector.updateOne() to update the lastLogin time. Lastly, since all the functions were executed successfully, the POST /login route will return a success response from res.send().

Test The code

Run npm start from your Terminal. Then put the credentials username and password as a JSON object. Finally, hit POST /login via Postman, and you will see these kind of screenshots should you code it correctly:

Logged in successfully
Failed to login because of wrong username or password.
Failed to login because the password field is missing.
Notes

I am aware it’s not secure yet because the password is not encrypted, and the user is not exactly logged in because there is no sessions here. We will get to that later on. Let’s just take one step at a time.

Add JWT

We will now create a JSON web token generator and checker functionality using the jsonwebtoken library.

authUtils.js

Create a new file named authUtils.js which contains the JWT module. See this snippet below:

// authUtils.js

const jwt = require('jsonwebtoken');

const createToken = (key, data, expiry) => {
  return jwt.sign(data, key, {
    expiresIn: expiry
  });
};

module.exports = {
  createToken
};

First, we import the jsonwebtoken module. Then, we create a function called createToken(). It will return a token generated from jwt.sign() with the arguments of data, key, and an object which currently only has the property of expiresIn.

.env and .env.example

On .env, add some new environment variables for generating new tokens. See this snippet below:

...
AUTH_KEY=cil-nodejs-api
AUTH_EXPIRY=600000

...

AUTH_KEY is the key for creating a new token, and AUTH_EXPIRY is the time needed for the token to expire in milliseconds. That means 600000 is 10 minutes.

Index.js

Open index.js to import authUtils. We will use the newly created module to create a new token when login is successful. See the snippet below:

// index.js

const config = {
  app: {
    ...,
    auth: {
      key: env.AUTH_KEY,
      expiry: env.AUTH_EXPIRY
    }
  },
  ...
}

...
const { ObjectId } = require('mongodb');
const authUtils = require('./authUtils');

...

app.post("/login", async (req, res) => {
  ...
  if (username && password) {
    ...
    if (result !== 'Data not found.') {
      const { auth } = config.app;
      ...
      const token = authUtils.createToken(auth.key, { username, password }, auth.expiry);
      res.send({
        status: 'success',
        message: `Logged as ${username}`,
        token
      });
    }
  }
  ...
});

...

First, we import the new environment variables as config.app.auth.key and config.app.auth.expiry.

Second, we import the previously created authUtils with const authUtils = require('./authUtils');

Third, on app.post("/login"), if the login credentials match, we create a new token with a key from config.app.auth.key, data from { username, password }, and expiry from config.app.auth.expiry.

Finally, if the token is created successfully, it will be returned as a response alongside status and message.

Test the code

Run npm start on your Terminal, then start the Postman app to hit POST /login. If everything goes well, you will see this kind of result:

A token is returned upon successful login.
Verify Token via Middleware

Next, we will add a middleware to all the routes for checking whether the token is valid on request.

authUtils.js

First, we add verifyToken() to check if the token is valid. See this snippet below:

// authUtils.js

const jwt = require('jsonwebtoken');

const verifyToken = (token, key) => {
  return jwt.verify(token, key);
};

...

Yeah, it’s a simple code. We create a function named verifyToken() and just return the result from jwt.verify(). The result is the data you create from createToken().

authMiddleware.js

Next, we create a middleware module called authMiddleware.js. We will add this same middleware to every routes except POST /login and POST /logout.

const { verifyToken } = require('./authUtils');

const authMiddleware = (req, res, next) => {
    const { auth } = req.headers;
    const { config } = req.app;

    try {
        verifyToken(auth, config.app.auth.key);
        next();
    }
    catch (err) {
        res.status(401).send(err);
    }
};

module.exports = authMiddleware;

We import verifyToken() from authUtils. Then we create a function called authMiddleware(). This function needs three parameters: req, res, next. Every middleware functions requires these three parameters, at least in Express.

Inside the function, we extract auth from req.headers. This means we will put auth as one of the headers on requesting on the route which passes this middleware.

Then, we get config from req.app. Yes, this is the configuration from loading environment variables. We will cover on how to set this later on index.js.

Finally, make a try-catch function. In the try block, call verifyToken() to verify the token’s validity. If it’s successful, it passes the authentication. But, if it fails, the catch block will return a response that the token verification failed.

index.js

Last part, we are back to index.js, where we will create a new config middleware, then apply the middlewares on the routes we created. Just for example only, I will only cover one route: GET /user, because the rest of the routes (POST, PATCH, DELETE) can also be applied in the same easy way. See this snippet below:

// index.js
...

const authUtils = require('./authUtils');
const authMiddleware = require('./authMiddleware');

...

const configMiddleware = async (req, res, next) => {
  Object.assign(req.app, { config });
  next();
};

...

app.get('/user', configMiddleware, authMiddleware,
  async (req, res) => {
    const result = await mongoDbConnector.find(collection, {});
    res.send(result);
  }
);

...

First, we import authMiddleware we created before.

Remember that we created config object to save environment variables? Now we want to make that config able to be accessed in any route we want. So, we create a new middleware to assign config to req.app. The routes that access this middleware will be to able to get req.app.config.

Lastly, we modify app.get("/user") to execute configMiddleware first, then authMiddleware, then the handler to load the data from MongoDB and return the response.

Test the code

Test the code with npm start. Open your Postman and hit GET /user. If everything went smoothly for you, you should get some results like these:

TokenExpiredError will be thrown if it’s expired.
JSONWebTokenError will be thrown if the token string is invalid.
JSONWebTokenError will also be thrown if the “auth” header is not provided.

So, to pass this error, you have to login first by hitting POST /login. Then, get the token from here:

That’s the token to put to “auth”

Put the token to the auth header of GET /user, and it should return a success response.

Finally, a success response!

Conclusion

Congrats! You have reached the ending. A quick recap of what we have learned:

  • Installing jsonwebtoken library.
  • Implement createToken() on successful login
  • Implement verifyToken() on any route.
  • Learn some general errors in token verification.

It would seem rather complicated to add an authentication token, but it’s not, actually. Because by using a middleware, you can authenticate every route you want.

The JWT in this tutorial is the simplest form of authentication. You can read more about other encryption provided by jsonwebtoken, and find which kind you need for your system.

Finally, here is the source code for this post. Let me know what you missed, or what I missed in the comments!


As you may have noticed, there are some issues in the code such as the user’s password are not encrypted in the database or some structure inconsistencies such as configMiddleware is index.js, while authMiddleware is separated on another file.

So, as you may also have guessed, the next part is about refactoring for more structured and readable code. Stay tuned!

Next post in the series:
Part 5 – Code Refactor Because It’s Starting To Get Complicated

“Human security depends on a system where each rational individual calculates that it is more profitable not to rebel.”

― Mark Gough

By Ericko Yap

Just a guy who is obsessed to improve himself. Working as a programmer in a digital banking company. Currently programming himself in calisthenics, reading books, and maintaining a blog.

Leave a Reply

Your email address will not be published. Required fields are marked *