Authorization middleware
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 presentfalse
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. |