Implementing JSON Web Tokens & Passport.js in a React App
Tags: express, javascript, jwt, node
Published: 2022-05-21 23:02:25

---
title: Implementing JSON Web Tokens & Passport.js in a React App
published: true
date: 2018-09-01 00:00:00 UTC
tags: express,javascript,jwt,nodejs
canonical_url: https://www.paigeniedringhaus.com/blog/implementing-json-web-tokens-passport-js-in-a-react-app
---

[![Passport, JWT, Express, and React logos](https://www.paigeniedringhaus.com/static/ee5d14c2643892ad0a4beb4a46375dab/1e043/passport-hero.png "Passport, JWT, Express, and React logos")](/static/ee5d14c2643892ad0a4beb4a46375dab/2cefc/passport-hero.png)

## Introduction

Before you say it, I know, I know. There’s already a literal ton of MERN tutorials out there showing how to use JSON Web Tokens (JWT, pronounced "jot") and Passport.js with Express.

But here’s what every one of those tutorials failed to mention, and what mine will cover: _how_ and _why_ to use the various authentication flavors Passport offers (including `passport-jwt`), and the gotchas that tripped me up for hours on end as I put together a user registration application.

I don’t know what your experience has been, but the majority of the projects I’ve worked on professionally have come with authorization already prebuilt by someone else. So in addition to improving my MERN skills, I viewed this as an opportunity to learn more about security and authentication and different ways to do it.

**This post will mainly cover how to implement of Passport and JWT on the server side, because that’s where most of the magic happens, but if you want, you can see the source files and download the full MERN project [here](https://github.com/paigen11/mysql-registration-passport).** 

Once it’s correctly hooked up on the backend, the frontend is purely React.

Before I jump into all that though, let me give you a quick run down of JWT and Passport.js authentication.

---

## What is a JSON Web Token?

[![JWT logo](https://www.paigeniedringhaus.com/static/5e49a7b09e89a1ae840c5546530d2d7c/1e043/jwt.png "JWT logo")](/static/5e49a7b09e89a1ae840c5546530d2d7c/ea7fb/jwt.png)

A **[JSON Web Token](https://jwt.io/)**, according to the site is:

> “…an open standard ([RFC 7519](https://tools.ietf.org/html/rfc7519)) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.” — [JWT.io](https://jwt.io/)

Essentially, JWTs are digitally signed tokens that can be verified and trusted, and they’re becoming more and more popular for security and authorization between parties (like servers and clients) and for exchanging sensitive information, while verifying the token hasn’t been tampered with or decoded.

I won’t go into the details of how the tokens work, you can read all about that from a much more qualified source than I am [here](https://jwt.io/introduction/), but suffice it to say, I chose to use JWT as part of my user verification strategy along with Passport.js to make my simple user registration application secure (and because I was curious to see if I could do it, since I didn’t have a whole lot of hands on experience implementing authorization).

With that covered, the next piece of my authentication solution is Passport.js.

## What is Passport.js & Why Should I Use it?

[![Passport logo](https://www.paigeniedringhaus.com/static/278b555d574a51fcd97b9201fd9366cf/772e8/passport.png "Passport logo")](/static/278b555d574a51fcd97b9201fd9366cf/772e8/passport.png)

**[Passport.js](https://www.passportjs.org/)** is:

> Simple, unobtrusive authentication for Node.js — [Passport.js](https://www.passportjs.org/)

And it works exceedingly well with Express.js. Passport is authentication middleware for Node, which serves one purpose, to authenticate requests, in a modular way that leaves all other functionality to the application itself, making code cleaner, easier to maintain and provides a clear separation of concerns.

If you type the term "JavaScript authentication middleware" or even just "JavaScript authentication" into the Google search bar, Passport.js ranks within the top 5 search results. That’s how ubiquitous this solution is in the JavaScript ecosystem.

Did I mention it boasts more than 500+ authentication strategies? It does. Whether you wish to log in with a simple username and password, with Github credentials, Facebook, oAuth, etc., there’s probably a Passport.js strategy for it.

So it was a simple decision to choose Passport as part of my authorization strategy.

I’d like to add documentation on the site is pretty good, though some things like custom callbacks require much more careful reading (and trial and error, for me) to correctly set up. But I’m getting ahead of myself, I’ll cover the gotchas in implementation.

## How Do I Implement Them in Express.js (and a little React)?

Now on to the fun part, how do you implement both Passport.js and JWT into an Express/Node application? To be honest, it stumped me for a good while. But after numerous tutorials, re-readings of documentation and turning to Stack Overflow for help, I got to a place of understanding and satisfaction.

I should say, one of my goals for this app was to make it modular, make it super simple to add or remove functionality, so when you see my file structure, you’ll notice all the routes for the CRUD functionality (create, read, update, delete) are built into separate files, and all the Passport authentication is handled in one, centralized file as well. Here’s a look at the file structure of the API portion of my app.

### The API File Tree

```
root/
├── api/ 
| ├── config 
| | ├── passport.js 
| | ├── jwtConfig.js
| ├── server.js 
| ├── sequelize.js 
| ├── package.json
| ├── models/ 
| | ├── user.js
| ├── routes/ 
| | ├── deleteUser.js 
| | ├── findUsers.js 
| | ├── loginUser.js 
| | ├── registerUser.js
| | ├── updateUser.js
| ├── node-modules/
```

### The `package.json` File

And here is the `package.json` and its dependencies so you can see exactly what I’m using.

**[`package.json`](https://github.com/paigen11/mysql-registration-passport/blob/master/api/package.json)**

```javascript
{
  "name": "mysqlregistration-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon server.js --exec babel-node --presets es2015"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/paigen11/mysqlRegistration.git"
  },
  "author": "Paige Niedringhaus",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/paigen11/mysqlRegistration/issues"
  },
  "homepage": "https://github.com/paigen11/mysqlRegistration#readme",
  "dependencies": {
    "babel-cli": "^6.26.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1",
    "bcrypt": "^3.0.2",
    "body-parser": "^1.18.3",
    "cors": "^2.8.4",
    "dotenv": "^6.1.0",
    "express": "^4.16.3",
    "helmet": "^3.20.1",
    "jsonwebtoken": "^8.3.0",
    "morgan": "^1.9.0",
    "mysql2": "^1.5.3",
    "nodemailer": "^4.6.8",
    "nodemon": "^1.18.3",
    "passport": "^0.4.0",
    "passport-jwt": "^4.0.0",
    "passport-local": "^1.0.0",
    "sequelize": "^5.3.0",
    "sequelize-cli": "^4.0.0",
    "swagger-jsdoc": "^3.2.9",
    "swagger-ui-express": "^4.0.2"
  }
}
```

The dependencies include a few extras like `babel` so I can use ES6 syntax in my Node.js app, `bcrypt` for password hashing and `sequelize` as my MySQL ORM, but the things you need to focus on are `jsonwebtoken`, `passport`, `passport-local` and `passport-jwt`. These are the necessities for this blog.

### The `server.js` File

I’ll start with the `server.js` file first, as it requires the least explanation. This file is purely for starting the server, initializing the use of Passport in the app and setting up the routes and parsing of requests from the client side.

**[`server.js`](https://github.com/paigen11/mysql-registration-passport/blob/master/api/server.js)**

```javascript
import express from 'express';
import Cors from 'cors';
import bodyParser from 'body-parser';
import logger from 'morgan';
import passport from 'passport';
import helmet from 'helmet';

const app = express();

const API_PORT = process.env.API_PORT || 3000;

require('./config/passport');

app.use(Cors());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(logger('dev'));
app.use(helmet());
app.use(passport.initialize());

require('./routes/loginUser')(app);
require('./routes/registerUser')(app);
require('./routes/findUsers')(app);
require('./routes/deleteUser')(app);
require('./routes/updateUser')(app);

// eslint-disable-next-line no-console
app.listen(API_PORT, () => console.log(`Listening on port ${API_PORT}`));

module.exports = app;
```

A little ES6 in the `imports` and `consts`, but what you should focus on is the `passport.initialize()` and the `routes`.

Pretty straightforward right? Great. Moving on.

You might also have noticed the `require('./config/passport');`, this is the next step I’ll go over. The JWT and Passport configuration, inside of the folder named `config/`.

### The `jwtConfig.js` File

The JWT config is very simple, it’s the secret required by JWT to encode and decode the tokens. Typically, this would be stored as an environmental variable in a file that’s not checked in to Github, but to show how this works, I’ve set it in here.

**[`jwtConfig.js`](https://github.com/paigen11/mysql-registration-passport/blob/master/api/config/jwtConfig.js)**

```javascript
module.exports = {
  secret: 'jwt-secret',
};
```

### The `passport.js` File

**[`passport.js`](https://github.com/paigen11/mysql-registration-passport/blob/master/api/config/passport.js)**

```javascript
import bcrypt from 'bcrypt';
import jwtSecret from './jwtConfig';

const BCRYPT_SALT_ROUNDS = 12;

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const JWTstrategy = require('passport-jwt').Strategy;
const ExtractJWT = require('passport-jwt').ExtractJwt;
const User = require('../sequelize');

passport.use(
  'register',
  new LocalStrategy(
    {
      usernameField: 'username',
      passwordField: 'password',
      passReqToCallback: true,
      session: false,
    },
    (req, username, password, done) => {
      try {
        User.findOne({
          where: {
            [Op.or]: [
              {
                username,
              },
              { email: req.body.email },
            ],
          },
        }).then(user => {
          if (user != null) {
            console.log('username or email already taken');
            return done(null, false, {
              message: 'username or email already taken',
            });
          }
          bcrypt.hash(password, BCRYPT_SALT_ROUNDS).then(hashedPassword => {
            User.create({
              username,
              password: hashedPassword,
              email: req.body.email,
            }).then(user => {
              console.log('user created');
              return done(null, user);
            });
          });
        });
      } catch (err) {
        return done(err);
      }
    },
  ),
);

passport.use(
  'login',
  new LocalStrategy(
    {
      usernameField: 'username',
      passwordField: 'password',
      session: false,
    },
    (username, password, done) => {
      try {
        User.findOne({
          where: {
            username,
          },
        }).then(user => {
          if (user === null) {
            return done(null, false, { message: 'bad username' });
          }
          bcrypt.compare(password, user.password).then(response => {
            if (response !== true) {
              console.log('passwords do not match');
              return done(null, false, { message: 'passwords do not match' });
            }
            console.log('user found & authenticated');
            return done(null, user);
          });
        });
      } catch (err) {
        done(err);
      }
    },
  ),
);

const opts = {
  jwtFromRequest: ExtractJWT.fromAuthHeaderWithScheme('JWT'),
  secretOrKey: jwtSecret.secret,
};

passport.use(
  'jwt',
  new JWTstrategy(opts, (jwt_payload, done) => {
    try {
      User.findOne({
        where: {
          id: jwt_payload.id,
        },
      }).then(user => {
        if (user) {
          console.log('user found in db in passport');
          done(null, user);
        } else {
          console.log('user not found in db');
          done(null, false);
        }
      });
    } catch (err) {
      done(err);
    }
  }),
);
```

Yes, all this is part of the `passport.js` file.

I realize this is a lot of code to take in. If you’d like to see just the code for Passport and the routes with Passport authentication, I created a few gists **[here](https://gist.github.com/paigen11/c72c8c20da9cd440f45025a1b05e5e58)** with the code.

I’ll walk through the two types of Passport implementation happening here: **[passport-local](https://www.passportjs.org/packages/passport-local/)** for the `register` and `login` methods and **[passport-jwt](https://www.passportjs.org/packages/passport-jwt/)** for the `jwt` method.

`passport-local` uses a username and password, and `passport-jwt` uses a JWT payload to verify the user is legit.

Like I said earlier, the documentation for both using just Express.js is fairly good, but the trick comes in combining it with the MySQL database and the information passed from the React client on the frontend.

Once the `passport-local` strategy has been passed a username and password (which I verify that both inputs are at least filled on the client side before ever calling back to the server), the first check I do on the backend is with [Sequelize](https://sequelize.org/), my SQL ORM (like Mongoose for MongoDB), to determine if that username exists in the database. If it returns `null`, the authentication fails (no user in the db matches), and typically, a `401 Unauthorized` HTTP status would be thrown back from the server to the client with no further information.

### Passport.js Gotcha #1: Info in Error Handling

This type of error handling, to me, was the most frustrating thing about Passport. The lack of information to let a user (or myself) know what the actual error was beyond `401 Unauthorized`.

But there’s a better solution that requires a little extra work, with **custom callbacks**. I’ll go into more detail about the callback soon, but for now, you can see if there’s some sort of error, instead of returning a `return done(null, user);`, Passport can pass back `return done(null, false, { message: 'bad username or passwords don't match' })`;.

This message can then be passed back from the server to the client, actually telling the user what the problem is (which is what I wanted for this app). If you prefer just to tell the user their authentication failed, but not disclose why, that’s cool too. Your choice, but I want to show how error messaging can be done.

The rest of the `passport-local` strategy is pretty self-explanatory, on `register` the user’s password is hashed and salted with the encryption package **[`bcrypt`](https://github.com/kelektiv/node.bcrypt.js)**, and then when the `login` method is invoked, it hashes the newly entered password and checks the passwords with bcrypt’s `compare` function before returning either a positive or negative on the verification.

Now that brings me to the `passport-jwt` strategy, which is named `jwt`. This is the authentication that’s called on the protected routes in the application: `findUsers`, `updateUser`, `deleteUser`.

For the jwt strategy, the JWT is passed back from the client with each call to the server (I passed mine in an authorization header with the key: `JWT`), which is extracted and then decoded using the secret (which is stored in another file, but should really be an environment variable known only to the system).

Once the JWT payload is decrypted, the ID (which for me is the username) can be searched for in the database just like the `passport-local` strategy does, and either returned with `done(null, user);`, if the user is found or with `done(null, false);` if it’s not (which should almost never happen because the JWT includes the username in its encrypted form, so unless that’s somehow been tampered with or the db has been, it should be able to find the user).

### Passport.js Gotcha #2: Passport-Local Wants Return, Passport-JWT Does Not

This brings me to my second gotcha which tripped me up for a good bit of time; not all passport strategies require the same resolution.

If you’re looking closely, you’ll see the `passport-local` strategies both have `return done(null, user);` but the `passport-jwt` strategy has `done (null, user);`. See the difference? It’s minuscule, but having (or removing that `return`), is the difference between that user data being passed back from the middleware to the server or not.

And it halted my progress for a good few hours, before the kindness of Stack Overflow helped me work through the issue (it’s the first time I’ve actually had to ask SO for an answer I couldn’t find already, but it was well worth it). So be aware of when to `return` or not.

Ok, so I’ve walked through the two Passport strategies and the three methods I’m using for middleware authentication in my program, now on to after the authentication on the server.

### The `register.js` File

The next step is how to implement these newly minted methods inside of the various routes. Here’s the `registerUser` route.

**[`registerUser.js`](https://github.com/paigen11/mysql-registration-passport/blob/master/api/routes/registerUser.js)**

```
import passport from 'passport';
import User from '../sequelize';

module.exports = app => {
  app.post('/registerUser', (req, res, next) => {
    passport.authenticate('register', (err, user, info) => {
      if (err) {
        console.error(err);
      }
      if (info !== undefined) {
        console.error(info.message);
        res.status(403).send(info.message);
      } else {
        req.logIn(user, error => {
          const data = {
            first_name: req.body.first_name,
            last_name: req.body.last_name,
            email: req.body.email,
            username: user.username,
          };
          User.findOne({
            where: {
              username: data.username,
            },
          }).then(user => {
            user
              .update({
                first_name: data.first_name,
                last_name: data.last_name,
                email: data.email,
              })
              .then(() => {
                console.log('user created in db');
                res.status(200).send({ message: 'user created' });
              });
          });
        });
      }
    })(req, res, next);
  });
};
```

As you can see, this implementation of Passport looks a little different than most examples out there, and it’s because I’m using the custom callback version of Passport, which requires a closure call. I’m using this so that the error message that’s passed back if Passport’s authentication fails can be sent from the server to the client instead of the obscure `401 Unauthorized`.

Because this is being implemented as a callback (which is why you see `(req, res, next)` not once, but twice in these scripts; it gives me access to the `(err, user, info)` from the Passport middleware.

The info is what I’m interested in — that’s the message being sent back, so if the info is anything besides `null`, that means the authentication failed and I can then send the message to the client to let them know why.

### Passport.js Gotcha #3: Don't Forget `req.logIn()` in Your Custom Callback

Now, I’m to the third Passport gotcha, in order to make the closures and custom callbacks work with `passport-local`, the little method **[`req.logIn()`](https://www.passportjs.org/concepts/authentication/login/)** must be called before the `user` data is handled if it comes back successfully from the middleware.

> This is documented in the [Passport documentation](https://www.passportjs.org/concepts/authentication/login/), but it’s importance is not stressed as much as I would like (and I missed it the first few times trying to get custom callbacks to work), which is why I highlight it now.

It must happen, and once that’s done, I’m able to take the extra inputs from the client side for registering a user, find that same user created in the database during the `passport-local` `register` call, and update the user file with the extra info. I could have passed this extra data through to the middleware as well, but I want Passport to only handle authentication, not user creation as well. Modularization, remember.

Plus, if the authentication were to be split out into a separate service with a separate database of just encrypted usernames and passwords, this would make it easier to do so, then use the username or ID to find and update the corresponding user record in this registration service.

Once all that’s been successfully taken care of, a `200` HTTP status and success message are sent back to the client.

### The `loginUser.js` File

Seeing the `loginUser` route now should make more sense.

**[`loginUser.js`](https://github.com/paigen11/mysql-registration-passport/blob/master/api/routes/loginUser.js)**

```
import jwt from 'jsonwebtoken';
import passport from 'passport';
import jwtSecret from '../config/jwtConfig';
import User from '../sequelize';

module.exports = app => {
  app.post('/loginUser', (req, res, next) => {
    passport.authenticate('login', (err, users, info) => {
      if (err) {
        console.error(`error ${err}`);
      }
      if (info !== undefined) {
        console.error(info.message);
        if (info.message === 'bad username') {
          res.status(401).send(info.message);
        } else {
          res.status(403).send(info.message);
        }
      } else {
        req.logIn(users, () => {
          User.findOne({
            where: {
              username: req.body.username,
            },
          }).then(user => {
            const token = jwt.sign({ id: user.id }, jwtSecret.secret, {
              expiresIn: 60 * 60,
            });
            res.status(200).send({
              auth: true,
              token,
              message: 'user found & logged in',
            });
          });
        });
      }
    })(req, res, next);
  });
};
```

The same style of custom callbacks and closures are used, the biggest difference is that once the user is successfully verified and located in the database, the JWT token is generated using the `jwt.sign();` function, which sets the username as the ID passed in the JWT, and encrypted by the secret I set earlier.

Once again, if all this works successfully, a `200` HTTP status is sent with a boolean I named `auth` set to true for the client side, the newly generated token and a short login success message.

### The `findUsers.js` File

And last, but not least, here’s the `findUsers` route. I used the same `passport-jwt` authentication for the `updateUser` and `deleteUser` routes, so I’ll just show this one as an example.

**[`findUsers.js`](https://github.com/paigen11/mysql-registration-passport/blob/master/api/routes/findUsers.js)**

```javascript
import passport from 'passport';
import User from '../sequelize';

module.exports = (app) => {
  app.get('/findUser', (req, res, next) => {
    passport.authenticate('jwt', { session: false }, (err, user, info) => {
      if (err) {
        console.log(err);
      }
      if (info !== undefined) {
        console.log(info.message);
        res.status(401).send(info.message);
      } else if (user.username === req.query.username) {
        User.findOne({
          where: {
            username: req.query.username,
          },
        }).then((userInfo) => {
          if (userInfo != null) {
            console.log('user found in db from findUsers');
            res.status(200).send({
              auth: true,
              first_name: userInfo.first_name,
              last_name: userInfo.last_name,
              email: userInfo.email,
              username: userInfo.username,
              password: userInfo.password,
              message: 'user found in db',
            });
          } else {
            console.error('no user exists in db with that username');
            res.status(401).send('no user exists in db with that username');
          }
        });
      } else {
        console.error('jwt id and username do not match');
        res.status(403).send('username and jwt token do not match');
      }
    })(req, res, next);
  });
};
```

I used custom callbacks for all my Passport authenticated routes (I appreciate good, clear error handling), so this code style should look routine by now.

This time, when `passport.authenticate()` is called, I implement the JWT strategy defined in the `passport.js` file. The same `(err, user, info)` gets passed back from the middleware, but there’s no call from `req.logIn()` this time.

Instead, I just return the `user` object found during the authentication and pass all the required fields to the client along with the `auth` boolean and success message. The JWT token is still stored in local storage on the client side so there’s no need to regenerate or pass it to the client again as well. Which brings me to my final Passport gotcha.

### Passport.js Gotcha #4: Passing Authorization Headers Correctly

This one’s not exactly a Passport-specific gotcha, but it is yet another thing that tripped me up.

As I said, I pieced together my (now much more whole) understanding of JWT and Passport from a bunch of other tutorials and documentation, and one of those tutorials led me astray. It had me passing the JWT back to the client in something besides true authorization headers, which is the way that I chose to have my `passport-jwt` strategy extract the JWT payload from the client request.

For this reason, I wanted to touch briefly on one piece of the frontend code, where I pass back the token in the proper format, so you don’t have to waste the time debugging like I did.

**[`Profile.js`](https://github.com/paigen11/mysql-registration-passport/blob/8d94f0289ec97d86683049b1dc06efa37f95f474/client/src/containers/Profile.js)**

```javascript
 async componentDidMount() {
    const accessString = localStorage.getItem('JWT');
    const {
      match: {
        params: { username },
      },
    } = this.props;

    if (accessString == null) {
      this.setState({
        isLoading: false,
        error: true,
      });
    } else {
      try {
        const response = await axios.get('http://localhost:3003/findUser', {
          params: {
            username,
          },
          headers: { Authorization: `JWT ${accessString}` },
        });
        this.setState({
          first_name: response.data.first_name,
          last_name: response.data.last_name,
          email: response.data.email,
          username: response.data.username,
          password: response.data.password,
          isLoading: false,
          error: false,
        });
      } catch (error) {
        console.error(error.response.data);
        this.setState({
          error: true,
        });
      }
    }
  }
```

I’m using the popular promise-based HTTP client **[Axios](https://www.npmjs.com/package/axios)** to make my server calls, but what you really need to notice is the `headers` section.

Before the call to the server happens, I extract the JWT from local storage using `localStorage.getItem('JWT')` (the key I set with the JWT value when I passed the token forward from the `loginUser()` route on the server side), and then I set it using `headers: { Authorization: `JWT ${accessString}` }` and a little ES6 string interpolation. That way, if my authorization headers had more than one value to parse out, it would still be easy for `passport-jwt` to extract the correct JWT payload info by finding the string associated with "JWT".

And that’s it, I just wanted to cover it because it blocked me for a while and only through API testing with **[Insomnia](https://insomnia.rest/)**, could I confirm my JWT token was actually working, and suss out that I was passing authorization headers from the client side incorrectly.

---

## Conclusion

I know I wrote a lot (and provided a bunch of code snippets and gists), but authentication is not a simple concept. Nor is it truly possible to really make an app so secure no one will ever be able to hack it. But if you take precautions like JWT and Passport.js authentication middleware, your Node.js app should have a better chance to withstand people with malicious intent.

Just be sure to look out for the gotchas I highlighted around Passport — and you’ll be on your way to a more secure site.

Check back in a few weeks — I’ll be writing more about JavaScript, React, IoT, or something else related to web development.

Thanks for reading, I hope this proves helpful and gives you a better understanding of implementing Passport authentication and using JSON web tokens.

If you’d like to make sure you never miss an article I write, sign up for my newsletter here: https://paigeniedringhaus.substack.com

---

## Further References & Resources

- [Passport.js](http://www.passportjs.org/) website
- [JSON Web Tokens](https://jwt.io/) website
- [MERN App with Passport & JWT repo](https://github.com/paigen11/mysql-registration-passport)
- [Gists](https://gist.github.com/paigen11/c72c8c20da9cd440f45025a1b05e5e58) of Passport implementation with routes
- [Axios](https://www.npmjs.com/package/axios) HTTP library