Harnessing the magic of Hapi scopes

A fantastic, but often underused, feature of hapi is the ability to use scopes for authorization.

In many frameworks in order to specify who has access to what resources, you have to perform manual checks within your route handlers. This can be a hassle since often these checks will be performed in many different routes. Sure, we can abstract them into a plugin or middleware, but with hapi we don’t have to since it’s all built right in.

Want to specify a route that only admins can access? It’s as simple as configuring it like this:

server.route({
    method: 'GET',
    path: '/something/adminy',
    config: {
        handler: function (request, reply) {
            return reply('Hello there, admin.');
        },
        auth: {
            scope: 'admin'
        }
    }
})

The “scope” field here tells hapi to check your authenticated user’s credentials for the given “admin” scope. If your user’s credential object looks something like this:

{
    "username": "jabba",
    "scope": ["admin", "hutt"]
}

Then the user will be authorized and will receive the “Hello there, admin.” message. However, if your user’s credential object lacked the “admin” scope, hapi would automatically reply with a 403 status code.

This is fantastic for static scopes, things like “admin” or “superuser,” but what if you need to know if a resource belongs to the user making the request? Well, good news. Recently, we got a pull request merged into hapi to allow the use of dynamic authorization scopes.

With this new feature, when a user authenticates you can pre-fetch a list of resources that belong to the user and add them to their scope. So let’s say you now have a user with a credentials object that looks like this:

{
    "username": "han",
    "scope": ["door-trash-compactor"]
}

Where “door-trash-compactor” is a dynamically generated scope representing the resource type (in this case, “door”) and “trash-compactor” represents the resource’s id. Now we can set up a new route to leverage our fancy new scope:

server.route({
    method: 'GET',
    route: '/doors/{door_id}',
    config: {
        handler: function (request, reply) {
            reply(request.params.door_id ' door is closed');
        },
        auth: {
            scope: ['door-{params.door_id}']
        }
    }
});

That magical little {params.door_id} part of the scope will be automatically expanded when hapi checks the scope to match the given request. So now if Han Solo were to make a request to /doors/trash-compactor the scope would expand to door-trash-compactor which is contained in Han’s credentials and he’ll receive the response “trash-compactor door is closed.” Neat, huh?

You might also enjoy reading:

Blog Archives: