A Step-by-Step Guide to Setting Up a Node.js API With Passport-JWT
Tags: tutorial, security, webdev, javascript
Published: 2020-08-22 21:37:56

![https://cdn-images-1.medium.com/max/1200/0*dPh6-JVay7r7HlJV.png](https://cdn-images-1.medium.com/max/1200/0*dPh6-JVay7r7HlJV.png)

Authentication and authorization are a huge part of applications. Whenever there’s an API route without protection or checks, an application can easily become a target for hackers. That’s why we need a secure token — the [JSON Web Token (JWT)](https://jwt.io/).

---

# **The Basics of JWT**

*I will not go too deeply into JWT, but here are all the basics.*

![https://cdn-images-1.medium.com/max/800/0*a5Oo7gACg1vPfTMo.png](https://cdn-images-1.medium.com/max/800/0*a5Oo7gACg1vPfTMo.png)

> JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

JSON web tokens encode and decode your user’s info. They’re used for authorization and information exchange.


They consist of three parts — header, payload, and signature — separated by dots (`.`) like this: `xxxxx.yyyyy.zzzzz`

Read more about JSON web tokens [here](https://jwt.io/introduction/).

---

# **Before You Begin**

I’ll assume your computer already has [npm](https://www.npmjs.com/) and testing with Postman, if not check out this [video](https://www.youtube.com/watch?v=t5n07Ybz7yI&feature=emb_logo).

Here’s the [code](https://github.com/calvinqc/trivin/tree/master/templates/node-passport-jwt) if you have any trouble with the process and join Trivin’s [slack](https://trivin.slack.com/) if you need to ask any questions.

---

# **Server Setup**

*Skip this step if you prefer to use your own server.*

If you don’t have a project, we’ll use [Trivin](https://github.com/calvinqc/trivin) to set up project templates. In this article, we’ll use it to create a simple-node-server.

```
$ npm i trivin -g
$ trivin server simple-node-server -g -i
```

This will create [a simple but well-structured node-server](https://medium.com/swlh/a-complete-guide-build-a-scalable-3-tier-architecture-with-mern-stack-es6-ca129d7df805), initialize Git, and install all the project dependencies.

---

### Installation

```
$ npm i passport passport-jwt winston cors express-validator jsonwebtoken
```

### Supporting Files Setup

```
$ mkdir store/ 
$ touch store/passport.js store/config.js store/utils.js controller/constant.js
```

### Constant.js

- First, there’s something I really like to do in the constant.js file. Instead of writing a lot of strings, I create *variables* for strings that I’m likely to re-use.
- Allow `TextEditor` to auto-complete for me and reduce typos in strings.
- Add these to the `constant.js` file:

```js
export const EMAIL_IS_EMPTY = 'EMAIL_IS_EMPTY';
export const PASSWORD_IS_EMPTY = 'PASSWORD_IS_EMPTY';
export const PASSWORD_LENGTH_MUST_BE_MORE_THAN_8 =
  'PASSWORD_LENGTH_MUST_BE_MORE_THAN_8';
export const WRONG_PASSWORD = 'WRONG_PASSWORD';
export const SOME_THING_WENT_WRONG = 'SOME_THING_WENT_WRONG';
export const USER_EXISTS_ALREADY = 'USER_EXISTS_ALREADY';
export const USER_DOES_NOT_EXIST = 'USER_DOES_NOT_EXIST';
export const TOKEN_IS_EMPTY = 'TOKEN_IS_EMPTY';
export const EMAIL_IS_IN_WRONG_FORMAT = 'EMAIL_IS_IN_WRONG_FORMAT';
```

### `utils.js`

- A file that stores all the functions and validations that are used throughout the project.
- It makes your code in the API Controller files much cleaner.

```js
import sha256 from 'sha256';
import { check } from 'express-validator';
import {
  PASSWORD_IS_EMPTY,
  PASSWORD_LENGTH_MUST_BE_MORE_THAN_8,
  EMAIL_IS_EMPTY,
  EMAIL_IS_IN_WRONG_FORMAT,
} from './constant';
export const generateHashedPassword = password => sha256(password);
export function generateServerErrorCode(res, code, fullError, msg, location = 'server') {
  const errors = {};
  errors[location] = {
    fullError,
    msg,
  };
return res.status(code).json({
    code,
    fullError,
    errors,
  });
}
// ================================
// Validation:
// Handle all validation check for the server
// ================================
export const registerValidation = [
  check('email')
    .exists()
    .withMessage(EMAIL_IS_EMPTY)
    .isEmail()
    .withMessage(EMAIL_IS_IN_WRONG_FORMAT),
  check('password')
    .exists()
    .withMessage(PASSWORD_IS_EMPTY)
    .isLength({ min: 8 })
    .withMessage(PASSWORD_LENGTH_MUST_BE_MORE_THAN_8),
];
export const loginValidation = [
  check('email')
    .exists()
    .withMessage(EMAIL_IS_EMPTY)
    .isEmail()
    .withMessage(EMAIL_IS_IN_WRONG_FORMAT),
  check('password')
    .exists()
    .withMessage(PASSWORD_IS_EMPTY)
    .isLength({ min: 8 })
    .withMessage(PASSWORD_LENGTH_MUST_BE_MORE_THAN_8),
];
```

### **Passport.js Setup**

- A node.js library that helps you with the authentication.
- Add this to your `store/passport.js`:

```js
import { Strategy, ExtractJwt } from 'passport-jwt';
import { config, underscoreId } from './config';
import { User } from '../database/models';

export const applyPassportStrategy = passport => {
  const options = {};
  options.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
  options.secretOrKey = config.passport.secret;
  passport.use(
    new Strategy(options, (payload, done) => {
      User.findOne({ email: payload.email }, (err, user) => {
        if (err) return done(err, false);
        if (user) {
          return done(null, {
            email: user.email,
            _id: user[underscoreId]
          });
        }
        return done(null, false);
      });
    })
  );
};
```

- `store/config.js` is where I keep all of my configurations of the app:

```js
export const config = {
  passport: {
    secret: '<Add_Your_Own_Secret_Key>',
    expiresIn: 10000,
  },
  env: {
    port: 8080,
    mongoDBUri: 'mongodb://localhost/test',
    mongoHostName: process.env.ENV === 'prod' ? 'mongodbAtlas' : 'localhost',
  },
};
export const underscoreId = '_id';
```

Modify `app.js` to use it with passport:

```js
import express from 'express';
import logger from 'winston';
import bodyParser from 'body-parser';
import cors from 'cors';

import passport from 'passport';
import mongoose from 'mongoose';

import { config } from './store/config';
import { applyPassportStrategy } from './store/passport';
import { userController } from './controller';

const app = express();

// Set up CORS
app.use(cors());

// Apply strategy to passport
applyPassportStrategy(passport);
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// API Route
app.use('/', userController);

/**
 * Get port from environment and store in Express.
 */
const { port, mongoDBUri, mongoHostName } = config.env;
app.listen(port, () => {
  logger.info(`Started successfully server at port ${port}`);
  mongoose
    .connect(mongoDBUri, { useNewUrlParser: true, useUnifiedTopology: true })
    .then(() => {
      logger.info(`Conneted to mongoDB at ${mongoHostName}`);
    });
});
```

# **Run Your App**

```
$ npm start
```

Let’s now go back and improve `user.controller.js` by applying `passport-jwt` to our API.

---

# **Apply Passport-jwt to Register/Login API**

![https://cdn-images-1.medium.com/max/800/0*1d_RhWhLZJvy0WFk.png](https://cdn-images-1.medium.com/max/800/0*1d_RhWhLZJvy0WFk.png)

Image Source: dotnettricks.com

```js
import express from 'express';
import jwt from 'jsonwebtoken';

import { validationResult } from 'express-validator';
import { config } from '../store/config';

import {
  generateHashedPassword,
  generateServerErrorCode,
  registerValidation,
  loginValidation,
} from '../store/utils';

import {
  SOME_THING_WENT_WRONG,
  USER_EXISTS_ALREADY,
  WRONG_PASSWORD,
  USER_DOES_NOT_EXIST,
} from '../store/constant';

import { User } from '../database/models';

const userController = express.Router();

const createUser = (email, password) => {
  const data = {
    email,
    hashedPassword: generateHashedPassword(password),
  };
  return new User(data).save();
}

/**
 * GET/
 * retrieve and display all Users in the User Model
 */
userController.get('/', (req, res) => {
  User.find({}, (err, result) => {
    res.status(200).json({ data: result });
  });
});

/**
 * POST/
 * Register a user
 */
userController.post('/register', registerValidation, async (req, res) => {
  const errorsAfterValidation = validationResult(req);
  if (!errorsAfterValidation.isEmpty()) {
    return res.status(400).json({
      code: 400,
      errors: errorsAfterValidation.mapped(),
    });
  } 
    try {
      const { email, password } = req.body;
      const user = await User.findOne({ email });
      
      if (!user) {
        await createUser(email, password);
 
        // Sign token
        const newUser = await User.findOne({ email });
        const token = jwt.sign({ email }, config.passport.secret, {
          expiresIn: 10000000,
        });
        const userToReturn = { ...newUser.toJSON(), ...{ token } };

        delete userToReturn.hashedPassword;

        res.status(200).json(userToReturn);
      } else {
        generateServerErrorCode(res, 403, 'register email error', USER_EXISTS_ALREADY, 'email');
      }
    } catch (e) {
      generateServerErrorCode(res, 500, e, SOME_THING_WENT_WRONG);
    }
});
/**
 * POST/
 * Login a user
 */
userController.post('/login', loginValidation, (req, res) => {
  const errorsAfterValidation = validationResult(req);
  if (!errorsAfterValidation.isEmpty()) {
    return res.status(400).json({
      code: 400,
      errors: errorsAfterValidation.mapped(),
    });
  } 
      const { email, password } = req.body;
      const user = await User.findOne({ email });
      if (user && user.email) {
        const isPasswordMatched = user.comparePassword(password);
        if (isPasswordMatched) {
          // Sign token
          const token = jwt.sign({ email }, config.passport.secret,         
          {
            expiresIn: 1000000,
          });
          const userToReturn = { ...user.toJSON(), ...{ token } };
          delete userToReturn.hashedPassword;
          res.status(200).json(userToReturn);
        } else {
          generateServerErrorCode(res, 403, 'login password error', WRONG_PASSWORD, 'password');
        }
      } else {
        generateServerErrorCode(res, 404, 'login email error', USER_DOES_NOT_EXIST, 'email');
      }
});
export default userController;
```

- Instead of using the user’s email and hashed password for authorization, which may not be secured during the communication between the client and the server.
- We use the JWT token for authorization. This way, we can ensure the security of the password and user’s email to be encrypted.

# **Testing**

- At this point, I’ll assume that you know how to use Postman.
- Use the `POST/` method and enter `localhost:8080/register` and `localhost:8080/login`.
- After you test your Register API, you’ll successfully get a result similar to below. Copy the token to your clipboard.

![https://cdn-images-1.medium.com/max/800/1*EuWQ1Mo_cJwQzsI_V2rSRQ.png](https://cdn-images-1.medium.com/max/800/1*EuWQ1Mo_cJwQzsI_V2rSRQ.png)

Register API Successful return a token and user’s email + id

# **Authorization**

Let’s see if you want to go to a specific link that requires the user to login. Then, you can simply add an authorization in the API.

Let’s look at an example.

- In `user.controller.js`, I include a simple `'/'` API that retrieves the list of all users — but I don’t want to retrieve all the users unless I log in as a user.
- Another example is Facebook. If you want to go to the news feed and retrieve all your posts, you need to be logged in.
- Here’s an example when you go to a secured API Route without a JWT Token (aka, you haven’t logged in):

![https://cdn-images-1.medium.com/max/800/1*Oft6XR83WafE79nMBVEjUA.png](https://cdn-images-1.medium.com/max/800/1*Oft6XR83WafE79nMBVEjUA.png)

An example with no JWT attached to the API

---

# **Authorization with Passport JWT**

Add these highlights to your `user.controller.js`:

```js
import express from 'express';
import jwt from 'jsonwebtoken';
import passport from 'passport';
import { validationResult } from 'express-validator';
...
/**
 * GET/
 * retrieve and display all Users in the User Model
 */
userController.get(
  '/',
  **passport.authenticate('jwt', { session: false }),**
  (req, res) => {
    User.find({}, (err, result) => {
      res.status(200).json({ data: result });
    });
  }
);
...
export default userController;
```

Now, test the API with Postman. Click on “Authorization”, and choose type “Bearer Token.” Then, paste your token in the token field and run:

![https://cdn-images-1.medium.com/max/800/1*y5u1ccu31puCLog8TPeOug.png](https://cdn-images-1.medium.com/max/800/1*y5u1ccu31puCLog8TPeOug.png)

With JWT, you’ll be able to retrieve all the users

---

# Well Done!

You’re now able to authorize and secure all other routes that require the user to login before using the API.

---

- Follow-up & connect with me on [Github](https://github.com/calvinqc) | [LinkedIn](https://www.linkedin.com/in/calvinqc/) | [Twitter](https://twitter.com/calvinqc_)