Authorization with Express

A student asked:

I am starting a new project using what I have learnt and I was wondering: what do you use in your projects to handle roles and permissions? Custom code? existing packages? I assume it has to be server side logic. Any suggestion is more than welcomed. Thanks again for the courses, I hope there is more to come!

Authentication vs. Authorization

First, it is important to note the difference between authentication and authorization. Authentication is used to establish and confirm the identity of a user. Authorization determines what an authenticated user has access to. It is easy to spot apps with authentication; generally, all you need to look for is a place to login or register. However, authorization comes in many forms, from only being able to delete your own comments, to seeing a different set of options in a navigation tab, to being able to view app analytics. The most common instance of authorization that you might see is the existence of "admin" priviliges in an app.

Authentication is a fairly standardized process. Using a Node backend, you will find that the Passport and Bcrypt libraries are popular choices for implemention. Unfortunately, finding libraries for authorization is more difficult because of how customized roles and permissions are. They need to be designed on a case by case basis to match the requirements of the app being built.

The Server is the Gatekeeper

Ultimately, secure and effective authentication and authorization systems MUST be implemented on the backend. As mentioned many times in the Advanced React Course, security has to be implemented by the server side, not by the client - you must always assume that users are able to circumvent any client-side security you set up.

Now, of course, you can have checks on the client side. It will provide faster performance for the user. But client-side checks can always be bypassed, so the server must act as the gatekeeper.

That being said, how can you start implementing an system of authorization?

Middleware to the Rescue

Express provides a lot of flexibility when handling requests. Often, handling requests looks like this:

app.get('/', (req, res) => {
  //do something and send a response
});

The flexibility lies in the fact that a sequence of functions can be added to the request handler:

app.get('/', doSomething, (req, res) => {
  //do something and send a response
});

const doSomething = (req, res, next) => {
  // do something with the request
  // then move to the next function
  next(); 
  // calling next is the only way move
  // to the next function
};

The function doSomething is called middleware. In general, it is useful when reusable logic is needed to run before the final route handler. Middleware is the perfect tool for adding authorization logic. You can even have multiple middlewares run, in order.

// 3 middlewares that will run in order
app.get('/',
  doSomething,
  doThisNext,
  andThenThis,
  (req, res) => {
  //do something and send a response
});

In the case above, the functions will run, in order, one after the other. Each middleware is passed the req, res, and next parameters. Most importantly, next() must be called at some point in each function so that Express knows to move to the next function.

Implementing Middleware

Using middleware, I would likely write a function to verify the user's role, then secure each route in the app with the appropriate middleware.

For example, I might have a requireAdmin middleware, that looks something like this:

const requireAdmin = (req, res, next) => {
  if (!req.user.isAdmin) {
    return res.status(401).send({ error: 'You are not authorized' });
  }

  next();
}

Or a middleware to assert that a resource (like a blog post) that a user is trying to edit belongs to them:

// Node 7.6+ is required to use async/await
const requireUserIsPostOwner = async (req, res, next) => {
  let post = await Post.find(req.params.id);

  if (req.user.id !== post.ownerId) {
    return res.status(401).send({ error: 'You do not own this post.' });
  }

  next();
}

Then you could mix and match these middlewares. If you had a route that allows a user to delete a post only if they are an admin and the owner of the post, you could do:

app.delete('/posts/:id', requireAdmin, requireUserIsPostOwner, (req, res) => { 
  // logic to delete post
});

The beauty of this approach is that each middleware is easily testable and can easily be composed into dramatically more complicated authorization structures. In addition, the reusability of middleware cut down on redundant code and makes it even more powerful:

app.use('/posts', requireAdmin, requireUserIsPostOwner, handleRoute);
// Every request to posts, including nested routes, will run the
// requireAuth and requireUserIsPostOwner middleware.

As you can see, Express middleware is an excellent option for handling authorization. It will significantly reduce the amount of code required in your route handlers, while making the development and maintenance of your application simpler.

Previous Post Next Post