DEV Community

loading...

Authentication in REST APIs

Dhruvang Gajjar
Full Stack Developer | JavaScript | Hubspot | APIs | React | Node | Data | Mongo
・8 min read

Built Authentication REST APIs using expressJS, MySQL, Sequelize and JWT. We will use following dependencies to built authentication APIs

Required Tool

Let’s take a moment to review the tools we’re going to be using:

  • NodeJS : We’re going to use this to run JavaScript code on the server. I’ve decided to use the latest version of Node, v6.3.0 at the time of writing, so that we’ll have access to most of the new features introduced in ES6.
  • Express : As per their website, Express is a “Fast, unopinionated, minimalist web framework for Node.js”, that we’re going to be building our Todo list application on.
  • NPM : for the package management (both server, frontend, and development packages). It’ll be easier to maintain one package management system, than using NPM and Bower together.
  • MySQL : This is a powerful open-source database that we’re going to use to store our Todos.
  • Sequelize : In addition, we’re going to use Sequelize, which is a database ORM that will interface with the Mysql database for us.
  • Postman : A Chrome app that we’ll use to practically test our API.

Create Project

Let’s begin by setting up our workspace.
You all are familiar with NPM. Before setup the project, open the terminal and check node and npm version. If version is displaying its means node and npm installed. If not then you must have to install the node and npm.

  • Open CLI and go to the project directory
  • Now type npm init to initialize the node project.

This command prompts you for a number of things, such as the name and version of your application. For now, you can simply hit RETURN to accept the defaults for most of them, with the following exception:

entry point: (index.js)

Enter app.js, or whatever you want the name of the main file to be. If you want it to be index.js, hit RETURN to accept the suggested default file name.
This command will generate package.json file in the project folder.

Setup Express

Initially I will make routes for the project. Install Express and a few of it’s dependencies.

  • Open CLI and go to the project directory
  • Type npm i --save express cors body-parser dotenv

The --save flag will save these packages to the dependencies section of your package.json file.

  • Create a file in the root folder and call it app.js.
  • In this file, let’s create our Express application.
const express = require("express"),
    bodyParser = require('body-parser'),
    cors = require('cors'),
    PORT = 8080;
require('dotenv').config()    

const app = express()
app.use(cors())
app.use(bodyParser.json())

app.get("/", (req, res) => {
    res.json({ "message": "Hello ChampDecay" })
})
app.listen(PORT, async () => {
    console.log(`App is running on http://localhost:${PORT}`);
})
Enter fullscreen mode Exit fullscreen mode

The application will be run scuccessfully on http://localhost:8080

We’ll need a way to restart the server every time we change something in our code. For that, we’ll use nodemon npm package.

npm i nodemon

Then, open up your package.json file and create a command to run the server. That command will be created under the scripts section. Edit your package.jsonin the scripts section as follows:

...
"scripts": {
    "dev": "nodemon app.js"
},
...
Enter fullscreen mode Exit fullscreen mode

Now try running the application by executing following command in cli.

npm run dev

and visiting http://localhost:8080. You should see

{
  "message": "Hello ChampDecay"
}
Enter fullscreen mode Exit fullscreen mode

At this point in time, your project structure should look like:

root
├── app.js
├── package.json
└── node_modules
Enter fullscreen mode Exit fullscreen mode

Sequelize Setup

For this part, we are going to install MySQL.
Next, we will require Sequelize. This is an ORM that will interface with the MYSQL database for us.
We will use of the Sequelize CLI package to bootstrap the project for us. It will also help us generate database migrations.

So Let’s install Sequelize CLI package.following command will install sequelize-cli globally

npm install -g sequelize-cli

  • Now we will install Sequelize package, alongside its dependencies. Sequelize is an easy-to-use multi SQL dialect ORM for Node.js. We gonna use MySQL as our database. So let install Sequelize ORM and mysql2 dialect. > npm i sequelize mysql2

Initialize sequelize

Let's generate generate migrations, seeders, config and models directories and config file using sequelize cli.

sequelize init

Above command will generate boilerplate code in your project, Now the project structure should look like:

root
├── app.js
├── package.json
├── config
│   └── config.json
├── migrations
├── models
│   └── index.js
└── seeders
Enter fullscreen mode Exit fullscreen mode

In models/index.js file, It establishes database connection by using config/config.json. So let's configure config.json.

{
  "development": {
    "username": "root", // Database Username
    "password": null,   // Database Password
    "database": "blog", // Database Name
    "host": "127.0.0.1",// Database Host
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now as we want to create a authentication, we have to create a table for Users. So we would generate migration and model for Users. Let's create model and migration by sequelize cli command as follows:

sequelize model:create --name User --attributes username:string,email:string,password:string

This will generate user.js file in model directory and <timestamp>-create-user.js migration in migration directory.

Generated user.js model file will be look like:

'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class User extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  };
  User.init({
    username: DataTypes.STRING,
    email: DataTypes.STRING,
    password: DataTypes.STRING
  }, {
    sequelize,
    modelName: 'User',
  });
  return User;
};
Enter fullscreen mode Exit fullscreen mode

and generated <timestamp>-create-user.js migration file will be look like:

'use strict';
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('Users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      username: {
        type: Sequelize.STRING
      },
      email: {
        type: Sequelize.STRING
      },
      password: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('Users');
  }
};
Enter fullscreen mode Exit fullscreen mode

Now we have to add some settings in model and migration files like unique key and allow/disallow null values. So let's modify user.js model file:

...
User.init({
    username: {
      type: DataTypes.STRING,
      unique: true,
      allowNull: false
    },
    email: {
      type: DataTypes.STRING,
      unique: true,
      allowNull: false
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    }
  }
...
Enter fullscreen mode Exit fullscreen mode

Here, We have added unique and allowNull in fields and same as model, add these parameters in migration file as well.

Finally, models/user.js will look like:

'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class User extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  };
  User.init({
    username: {
      type: DataTypes.STRING,
      unique: true,
      allowNull: false
    },
    email: {
      type: DataTypes.STRING,
      unique: true,
      allowNull: false
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    }
  }, {
    sequelize,
    modelName: 'User',
  });
  return User;
};
Enter fullscreen mode Exit fullscreen mode

and migrations/<timestamp>-create-user.js file will look like:

'use strict';
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('Users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      username: {
        type: Sequelize.STRING,
        unique: true,
        allowNull: false
      },
      email: {
        type: Sequelize.STRING,
        unique: true,
        allowNull: false
      },
      password: {
        type: Sequelize.STRING,
        allowNull: false
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('Users');
  }
};
Enter fullscreen mode Exit fullscreen mode

After setting up model and migrations, we will setup routes and controllers for user.

Setup Route

Create a file routes/user.js at root directory of the project.
It will be look like:

const express = require('express'),
    router = express.Router();

router.post('/signup', (req, res) => {
    res.json({ "msg": "Signup Route" })
});
router.post('/signin', (req, res) => {
    res.json({ "msg": "Signin Route" })
});

module.exports = router
Enter fullscreen mode Exit fullscreen mode

Here we have create two routes, one for signup and another for signin. now this file should be called in app.js

Add following code in app.js before app.listen command.

app.use('/api', require('./routes/user'))
Enter fullscreen mode Exit fullscreen mode

That's it! We have setup routes for signup and signin. Our next step will be settingup the controller for user.

Setup Controller

Here, we will use jsonwebtokens for api authentications, So let's install bcryptjs and jsonwebtoken packages.

npm i bcryptjs jsonwebtoken

After adding two packages, create a controllers/user.js file and add following code:

const bcrypt = require("bcryptjs"),
    jwt = require('jsonwebtoken'),
    db = require("../models/index"),
    JWT_SECRET = process.env.JWT_SECRET

exports.signUp = async (req, res) => {
    const { username, email, password: plainTextPassword } = req.body;
    const password = await bcrypt.hash(plainTextPassword, 10)
    try {
        const user = await db.User.create({
            username,
            email,
            password
        })
        res.status(201).json({ "status": "ok", "message": "User registered", user })
    } catch (error) {
        res.status(401).json({ "status": "error", "message": error.errors[0].message })
    }
}

exports.signIn = async (req, res) => {
    const { email, password } = req.body;
    const user = await db.User.findOne({ where: { email: email } })
    if (!user) {
        return res.status(401).json({ status: 'error', error: 'Invalid username/password' })
    }
    if (await bcrypt.compare(password, user.password)) {
        const payload = { id: user.id, username: user.username };
        const options = { expiresIn: '2d', issuer: 'http://localhost:8080' };
        const secret = JWT_SECRET;
        const token = jwt.sign(payload, secret, options)
        return res.status(200).json({ status: 'ok', "message": "User signin successful", token })
    }
    return res.status(401).json({ "status": "error", "message": "Invalid Username/Password" })
}
exports.getUsers = async (req, res) => {
    try {
        const users = await db.User.findAll()
        return res.status(200).json({ status: 'ok', users })
    } catch (error) {
        return res.status(401).json({ "status": "error", error })
    }
}
Enter fullscreen mode Exit fullscreen mode

In this file, we have imported bcryptjs, jsonwebtoken and index model. We have define JWT_SECRET variable which should be hidden and ideally fetch from .env file. Thereafter we have exported two functions.

Signup function

Here we get all request parameters and define it by const { username, email, password: plainTextPassword } = req.body; Then we have to hashed the password so no one can see it from database. To hash the password, we have used bcrypt's hash function. we have used 2 paramaters in hash function, first is plaintext password which should be encoded and second is salt.

After it, we have to store the values including new hashed password to the database. so using sequelize's create function we have stored it in databse.

Signin function

Here we get all request parameters same as signup function. Thereafter we fetch row from users table of the database using findOne function.
If it doesn't return any row that means a user enters wrong email so we have responsed invalid message wirh 401 status.
If It returns a row, we have to compare the password from database and user input. So again bcrypt will be used. the compare function of bcrypt will do the comparisation. If comparisation is true, we will generate access token using jwt otherwise returns error message with 401 status.
We have used username and id to create jwt token. Also we have set expire time and issuer of the token.

Get Users Function

This function simply fetch users from database. But to access this route, user should pass valid access token with the request and to validate the access token we have to make a middle ware.
Create a new file middleware/auth.js and that file should have following code:

const jwt = require('jsonwebtoken');

module.exports = {
    validateToken: async (req, res, next) => {
        const authHeader = req.headers.authorization;
        if (authHeader) {
            const token = authHeader.split(' ')[1];
            try {
                const result = await jwt.verify(token, process.env.JWT_SECRET)
                req.decoded = result;
                next()
            } catch (error) {
                return res.status(401).json({ "status": "error", "message": "Invalid Authentication.", error })
            }
        } else {
            return res.status(401).json({ "status": "error", "message": "Authentication error. Token required." })
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Here we have imported only jsonwebtoken and create a function called validateToken. It will take access token from the authorization headers and verify it using jwt.verify() function. If it is verfied successfully, it will go for the next process by next() function, otherwise it returns a error message of invalide access token with status code 401.

So finally we have created controller and route. Let's connect route with the controller. Open routes/user.js file and replace the following code:

const express = require('express'),
    router = express.Router(),
    User = require("../controllers/user")
router.post('/signup', User.signUp);
router.post('/signin', User.signIn);
router.get('/users', Auth.validateToken, User.getUsers);
module.exports = router
Enter fullscreen mode Exit fullscreen mode

Here in /users route, we have used middleware as second argument and That's all.

Finally, The code is ready..!!

This is my first post. Let me know your views by commenting on it.

Run in Postman

forthebadge

Gmail

Discussion (0)