Authorisation for a REST API deployed on Heroku

This week we have been tasked with building a REST API that returns JSON data for our group projects, and my group made one that contains data about books that our cohort has read.

We added user authentication for our POST routes so only logged in users can add resources to the database. We did this with token-based authentication using the jsonwebtoken Node.js npm package.

When a new user signs up to our API they get a JWT that is signed with a secret that we stored as an environment variable in our .env file.

const dotenv = require("dotenv");
const jwt = require("jsonwebtoken");
const secret = process.env.JWT_SECRET;

dotenv.config();

function signUp(req, res) {
  // run database query function that creates a new user
  .then((user) => {
    const access_token = jwt.sign({ user: user.id }, secret, { expiresIn: "1h" });
  user.access_token = access_token;
  // send user object
  }
  // error-handling
};

When the user signs in successfully by making a POST request to the API, we send the access_token property of their user object in the response body. The user can then send this JWT in the authorization headers when making a request to our API that requires authorization.

This was all working great on our local servers, but we were unable to sign up or sign in to our API once deployed on Heroku. We were scratching our heads until it dawned on me as I was biting into my sandwich at lunch: we needed to add our JWT_SECRET variable to our Config Vars on Heroku. Problem solved 🥳!

Operational vs. Programmer Errors

This week we did a research spike on error-handling to prepare us for this week’s Express project, and one of the topics we covered was the difference between operational and programmer errors.

Operational errors in your application are run-time errors that relate to the system’s configuration or network. For example, a failure to connect to the server, or a request timeout.

Programmer errors in your application are bugs that can be avoided by changing the code, often created by spelling mistakes. Examples include: missing or invalid arguments, mistyping a variable name, passing the wrong data type that what was expected, or calling an asynchronous function without a callback. These errors should not be handled because the code is broken so the program should not recover and keep handling requests.

We deployed a programmer error to our REST API today which broke our Heroku server because we forgot to uncomment an unused function that we had imported but not defined in our code. This was a mistake which we’ve learned the following from: always test our server runs before pushing changes to our master branch on GitHub, and learn more about continuous integration!

Faking HTTP requests in Node.js

We can use the nock library for Node.js to mock HTTP requests and receive fake network responses, so we can focus on testing our code rather than getting failing tests caused by poor connection to an API.

After you install the npm package, you can use nock like this to override Node’s http.request function in your server tests:

const nock = require("nock");

nock("https://api.com")
  .get("/path")
  .reply(200, { response object } );

Underscores in front of variables

Today we were learning about building REST APIs in Node using Express. In one of our functions we had to use a try...catch statement for our error handling.

We had to put an underscore in as an argument as catch cannot take an empty argument.

try {
  // try something here
} catch (_) {
  // error handling here
}

Routing in Express JS

Until today we have been using vanilla Node to build our web applications for workshops and projects in the course.

Today we learned how 3 words can make your life so much easier. Those words are: npm install express!

Until now we have been handling routes in our applications using the built-in Node HTTP server functionality, essentially writing long if statements that handle each path and request type:

const http = require("http");

const server = http.createServer((request, response) => {
  const url = request.url;
  if (method === "GET" url === "/") {
    // handling request
  } else if (method === "POST" url === "/submit") {
    // handling request
  }
};

This is made much simpler with the Express server object:

const express = require("express");
const server = express();

server.get("/", (req, res) => {
  // handling request
});

server.post("/submit", (req, res) => {
  // handing request
});

Never hash passwords synchronously

In our group project this week we used the bcrypt.js Node.js module to hash our users passwords before storing them in our database, specifically using the .hashSync() method:

const bcrypt = require("bcryptjs");

const salt = bcrypt.genSaltSync(10);
user.password = bcrypt.hashSync(user.password, salt);

In our code review our course leader pointed out that hashing is designed to take a long time for security purposes, so using the synchronous method of hashing will block our server code until the process is finished.

Instead we should do it asynchronously using promises:

const bcrypt = require("bcryptjs");

bcrypt.genSalt(10).then((salt) => bcrypt.hash(user.password, salt));

Finding unique items in arrays using the Set object

If we take the following array:
const arr = [4, 5, 4, 6, 3, 4, 5, 2, 23, 1, 4, 4, 4];

Instead of using filter on an array to find its unique values:

const unique = arr.filter((n, i, arr) => arr.indexOf(n) === i);

JavaScript has the Set object which stores unique values.

We can use Array.from to create a new array from the Set:

const unique = Array.from(new Set(arr));

Or we can use the spread operator to create a copy of the array:

const unique = [...new Set(arr)]

All of these evaluate to the same array of unique values:
console.log(unique); // [4, 5, 6, 3, 2, 23, 1]

Spread operator for objects

I’ve seen the spread operator many times, but haven’t needed to use it until today. This was the syntax:

function addNewProperty(object) { 
  return fetch("url")
    .then((property) => {
      const newObject = {...object}; 
      newObject.newProperty = property;
      return newObject; 
    })
}

{…object} creates a copy of the object passed into the addNewProperty() function. Then the property resolved from the promise is added to the copy and a new object is returned.

Advanced form validation

Until today I have only thought of form validation in terms of putting the required attribute on my <input> elements. This lets the native constraint validation behaviour of HTML5 do the work of checking that the user inputs are filled in and in the correct format.

Today we learned about the disadvantages of relying on this method, namely that the error messages are not accessible to screen readers and cannot be styled, and that inputs are invalid by default before the user has even had the chance to interact with them.

We can add custom validation with JavaScript, firstly we need to override the default form behaviour and prevent the in-built errors from appearing:
document.querySelector("form").setAttribute("novalidate", "");

Using the .checkValidity() method we can display custom error messages for invalid inputs. These error messages can now be styled in a meaningful way to the user, and aria labels can be added to make them accessible.

However, it is still good practice to have HTML constraint validation in case the JavaScript does not load or breaks.

Cookies in Node.js 🍪

In Node.js you can attach cookies to the server response using the Set-Cookie header in your router.js file:

response.writeHead(302, {
  'Set-Cookie': 'loggedin=true; HttpOnly; Max-Age=9000'});

You can set multiple cookies in an array:

response.writeHead(302, {
  'Set-Cookie': ['cookie1=example'], ['cookie2=example']});

You can remove the cookie by overwriting the values you’ve set or setting the Max-Age to 0:

response.writeHead(302, {
  'Set-Cookie': 'loggedin=0; HttpOnly; Max-Age=0'});

You can view cookies on the Chrome Dev Tools in the Application tab.