The authorization middleware will let you define organization and scope expectations on your route config and check requests against these expectations. Scopes are checked by @ambassify/shiro which implements Apache Shiro like permission checks.

Note: authorization will be skipped if a request is not authenticated (see options below.)

Defining organization expectations

The organization option lets you define the organization this middleware requires for a request to pass through it. It can be:

  • true to indicate an organization must be present
  • false to indicate an organization is not allowed
  • A Number to indicate which organization must be present
  • A function returning any of the above. If you pass a function, the organization expectation will be generated at request time by calling the function with one argument, the “context” (see below.)

Defining scope expectations

The scope option lets you define the scope this middleware requires for a request to pass through it. A full scope is defined as follows:

{
    allOf: [ 'a', context => `foo:${context.bar}` ],
    anyOf: [ 'b', 'c' ],
    noneOf: [ 'd' ]
}

You can make it more compact by passing a single string/function or an array of those and they will be treated as “allOf”, meaning all of them are required. “anyOf” means at least one of the scopes must be owned, “noneOf” means none of the scopes can be owned.

If you pass a function instead of a string, the scope will be generated at request time by calling the function with one argument, the “context” (see below.)

For example, the following code will use the userId query parameter.

ctx => `read:user:${ctx.query.userId}`

Request context

The context mentioned above is retrieved from the getRequestContext function you can pass in as an options when creating the middleware.

The default function if you do not pass in your own looks like this:

function defaultGetRequestContext(req, res) {
    const ctx = {
        params: req.params || {},
        query: req.query || {},
        body: req.body || {},
        credentials: get(req, 'context.credentials') ||
            get(res, 'locals.credentials') || {},
        context: req.context || {},
        req,
        res
    };

    return ctx;
}

Manual mode

Sometimes you cannot do the scope checks until you’ve done some extra processing on the request like for example retrieve the owner of a specific object from a database so you can match a scope against the owner’s id.

This can be accomplished by setting the manual option to true. When this is done, the middleware will not check the required scope when it runs, but simply parse and prepare it and then add some tools to the request or response object that will provide you with the means to execute the scope check later in your controller.

The setAuthorizationContext function that can be passed to options takes care of how the tools are added. If you do not override it, they will be added on req.context.authorization if you’re using the context middleware or res.locals.authorization otherwise. The function receives three parameters: request object, response object and an object containing the authorization tools.

The two tools that are added are addContext and verify.

req.context.authorization.addContext(key, value) will add an entry on the context that is used to stringify function-scopes.

req.context.authorization.verify() will kick off scope verification.

const myController = {
    authentication: true,
    authorization: {
        manual: true,
        scope: 'read:<%= fooId %>',
    },
    controller: (req, res) => {
        const fooId = getMyFooId();
        req.context.authorization.addContext('foo', fooId);
        req.context.authorization.verify();
    }
}

Options

Option Type Default Description
manual boolean false Enable or disable manual mode
organization See above. null The organization expectation for the request to pass through
scope See above. null The required scope for the request to pass through
getIsAuthenticated Function Function A function that is used to determine whether or not the current request is authenticated. It receives two parameters: the request and response objects. By default it checks for the presence of credentials at req.context.credentials or res.locals.credentials.
getRequestContext Function See above. A function that is used to build the context used to stringify function-scopes. It receives two parameters: the request and response objects.
getRequestScope Function Function A function that is used to determine the owned scope of the current request. It receives two parameters: the request and response objects. By default it checks for the presence of scope at req.context.credentials.scope or res.locals.credentials.scope.
getRequestOrganization Function Function A function that is used to determine the request’s current organization. It receives two parameters: the request and response objects. By default it checks for the presence of organization at req.context.credentials.organization or res.locals.credentials.organization.
setAuthorizationContext Function See above. Called to add the tools to do authorization in manual mode to your request or response object so they can be accessed in controllers. It receives two parameters: the request and response objects.
onInvalidOrganization Function Function Callback that is called when the middleware detects that the current organization does not satisfy the organization expectation. It receives two parameter: the expected organization and the actual organization. Throws an ForbiddenError by default.
onInsufficentScope Function Function Callback that is called when the middleware detects that the owned scope does not satisfy the requested scope. It receives one parameter: an object that contains the owned and required scope as { got, need }. Throws an InsufficientPermissionsError by default.