Mar 01 2020

Authentication with Higher Order Functions

I have a small Express.js side project that I occasionally pick up. I just got to the point where I need to enforce authentication over several routes. I implemented a simple higher order function (HOF) that loads an authenticated user into the req object.

When authentication fails, I throw an exception and fallback to my error handler. Introducing the HOF eliminates the need to copy-paste in the controller and thus keeps the code DRY.

I did not download a dependency. While I could use something like Passport, this would not become beneficial until several authentication strategies are needed. Even so, installing additional dependencies are carefully downloaded.

Before HOF:

const get = async (req, res) => {
  const [aErr, decoded] = await authenticate(
    _.get(req, 'headers.authorization', null)
  )
  if (aErr.length > 0) {
    throw new Exception(aErr)
  }

  const _instanceId = _.get(decoded, 'payload.id', null)
  // Do something with the verified Authorization HTTP header
  // In our case, we are grabbing the `id` out of our JWT payload
}

After HOF:

const withAuthentication = (cb) => async (req, res) => {
  const [aErr, decoded] = await authenticate(
    _.get(req, 'headers.authorization', null)
  )
  if (aErr.length > 0) {
    throw new Exception(aErr)
  } else {
    _.set(req, 'meta.token', decoded)
    cb(req, res)
  }
}

const get = withAuthentication(async (req, res) => {
  const _instanceId = _.get(req, 'meta.token.payload.id', null)
  // Do something with the verified Authorization HTTP header
  // In our case, we are grabbing the `id` out of our JWT payload
})

This also could have been implemented as middleware and registered on a per-route basis. Instead I chose the HOF for its explicit nature. Since the routing is declared in a separate file, using the middleware approach seemed to be more error-prone.

For controllers, I believe in “the thinner the better”. The reason is that you get performance gains when logic is moved out into its own isolated function. Typically a controller test will take between 15-30x longer to execute than your typical isolated function. Most of the spent time is in setting up and tearing down application instances.

Just imagine: for one controller test, you could have “paid” for 15-30 isolated function tests. Plus, isolated code naturally leads to better design. Which would you choose?