Skip to content

Instantly share code, notes, and snippets.

@cklanac
Last active November 3, 2018 08:54
Show Gist options
  • Save cklanac/79e272919b1b87faf778109420f0cbd8 to your computer and use it in GitHub Desktop.
Save cklanac/79e272919b1b87faf778109420f0cbd8 to your computer and use it in GitHub Desktop.
Challenge 17: JWT Authorization

Challenge - JWT Authorization

In this challenge you will update the /login endpoint to create and return a JWT. You will also create a Passport JWT Strategy and use it protect all the notes, folder and tags endpoints. And then use the JWT to access the protected endpoints.

Requirements

Configure JWT settings

Add JWT_SECRET and JWT_EXPIRY settings to config.js.

module.exports = {
  PORT: process.env.PORT || 8080,
  MONGODB_URI: process.env.MONGODB_URI || 'mongodb://localhost/noteful',
  TEST_MONGODB_URI: process.env.TEST_MONGODB_URI || 'mongodb://localhost/noteful-test',
  JWT_SECRET: process.env.JWT_SECRET,
  JWT_EXPIRY: process.env.JWT_EXPIRY || '7d'
};

Notice that JWT_SECRET does not have a fallback value so it must get the value from an environment variable. Let's set that up now.

There are a few ways to setup an environment variables like creating them in the shell or defining them in your .bash_profile or .bash_rc. We'll use the dotenv NPM package which allows you to define environment variable on a per-project basis.

First, install the npm package npm install dotenv. Then create a file named .env with the following, replacing <YOUR-SECRET> with a secret of your choosing.

JWT_SECRET=<YOUR-SECRET>

The JWT_SECRET is used by the JWT library to create secure token. It is essential that the value remains private, never commit and push the .env file to GitHub. Before proceeding, verify that .env is listed in the .gitignore file.

At the top of the config.js, require and config the dotenv package. At the top of the file, add the following:

require('dotenv').config();

Add JWT to /routes/auth.js router

Currently the /login endpoint returns the current user. You need to update it to return a JWT which contains the user information.

First, npm install the jsonwebtoken and require it. Then in /routes/auth.js file, require config.js and extract JWT_SECRET and JWT_EXPIRY using object destructuring. Then create a function which accepts a user object and calls jwt.sign() to generate a JWT. Below is an example.

function createAuthToken (user) {
  return jwt.sign({ user }, JWT_SECRET, {
    subject: user.username,
    expiresIn: JWT_EXPIRY
  });
}

Now update the /login endpoint, create a token by calling the createAuthToken function and return it to the user.

router.post('/login', localAuth, (req, res) => {
  const authToken = createAuthToken(req.user);
  res.json({ authToken });
});

Test the endpoint using Postman.

JWT strategy

Now that you can generate and return JWTs, you need to create a Passport JWT Strategy and use it protect endpoints.

First npm install the passport-jwt package.

Create a /passport/jwt.js file. In the file, require the passport-jwt package and extract Strategy and ExtractJwt from the the library. Also require the config.js and extract JWT_SECRET. Below is an example.

const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
const { JWT_SECRET } = require('../config');

Create an options object with the following settings. Then, using the options object create a new JWT strategy.

const options = {
  secretOrKey: JWT_SECRET,
  jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('Bearer'),
  algorithms: ['HS256']
};

const jwtStrategy = new JwtStrategy(options, (payload, done) => {
  done(null, payload.user);
});

Finally, export jwtStrategy so it can be required elsewhere.

Update server.js

Next on server.js, require the strategy and configure passport to utilize.

const jwtStrategy = require('./passport/jwt');
passport.use(jwtStrategy);

Protecting the Notes, Folders and Tags routers is easy. Simply add the following router.use call to each of the router files, be sure to require Passport.

// Protect endpoints using JWT Strategy
router.use('/', passport.authenticate('jwt', { session: false, failWithError: true }));

The basic JWT structure is now ready to go. Test it out using Postman. Create a new user and login. Then, using the Bearer Token authorization and the generated authToken, attempt to access the protected endpoints.

Add /refresh endpoint to /routes/auth.js

To finish the server-side implementation, you need to add a /refresh endpoint that allows users to exchange older tokens with fresh ones. Add the following to the /routes/auth.js.

const jwtAuth = passport.authenticate('jwt', { session: false, failWithError: true });

router.post('/refresh', jwtAuth, (req, res) => {
  const authToken = createAuthToken(req.user);
  res.json({ authToken });
});

Update Client

Finally, there are 2 updates to the client. First, are capturing the authToken and second, using it for all the api requests.

In the store.js file, add an authToken property and default it to an empty string.

  ...
  return {
    notes: [],
    folders: [],
    tags: [],

    currentNote: {},
    currentQuery: {
      searchTerm: '',
    },
    authToken: ''  // <<== Add this
  };

In noteful.js, find and update the handleLoginSubmit() function. Define an store.authToken property and set it to the incoming authToken value.

  ...
  api.create('/api/login', loginUser)
    .then(response => {
      store.authToken = response.authToken; // <<== add this
      store.authorized = true;
      loginForm[0].reset();

      return Promise.all([
        api.search('/api/notes'),
        api.search('/api/folders'),
        api.search('/api/tags')
      ]);
    })
    ...

Now, update all of the API methods to include the Authorization header with Bearer token.

In api.js, add the following to each API method.

  headers: { 'Authorization': `Bearer ${store.authToken}` }

Here is search as an example.

  const search = function (path, query) {
    return $.ajax({
      type: 'GET',
      url: path,
      dataType: 'json',
      data: query,
      headers: { 'Authorization': `Bearer ${store.authToken}` }
    });
  };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment