Authorization control
With this plugin, checking a user's authorization can be really easy. All it takes is this:
Design a hierarchy of features
When designing your application, you design different ways of interacting with its features. It may consist of several sections each offering some views with actions a user may trigger.
Example
An online shop has - among other things - a configuration section for setting up offered articles and a customers section for managing previous orders per customer.
You can organize those sections, their views and actions in a hierarchy of assumed features which might contain something like this:
Prepare for authorization control
When implementing those features, preparing your code for authorization control is as simple as adding a check like this one:
export async function addArticle( req, res ) {
const { Authorization, HttpExcetion } = req.api.service;
if ( !await Authorization.mayAccess( req.user, "configuration.articles.add" ) ) {
throw new HttpException( 403 );
}
// TODO add your implementation here
}
When using names similar to the designed features, the code is sort of turning those features a.k.a. resources into authorizations a requesting user may have access granted to or not. The dot-notation format is recognized in supporting a hierarchy of such resources.
Option: protect endpoints with policies
When implementing a REST-like API, authorizations required for either endpoint may be declared via policies rather than injecting tests into those endpoints' code as shown before:
export default function() {
const { mayAccess } = this.service.AuthorizationPolicyGenerator;
return {
"ALL /api/configuration": mayAccess( [ "joe.boss", "@manager" ] ),
};
}
The policy generated by the mayAccess() method rejects a request with a 403 Forbidden
status unless the requesting user is authenticated as joe.boss
or some user with role manager
. When assigned to certain routes as a policy, it applies to all endpoints sharing either route as a prefix. Limiting them by HTTP method is available this way, too.
Separating your code from such setups may be beneficial as it is enabling your application to have a more flexible authorization control which might be easier to maintain long term.
Configure authorization control
The code is now depending on an authorization granted to requesting users. By default, no authorizations are granted so that the condition is never met. Any request for an endpoint eventually invoking that method will be rejected with a 403 Forbidden
status.
Authorizations are granted to or revoked from users and roles either in your application's configuration or in a connected datasource. Let's try the former:
export const auth = {
authorizations: {
"configuration.articles.add": "joe.boss,@manager",
"configuration.articles.available": "joe.boss,@manager",
}
};
After restarting your application, requests by a user authenticated as joe.boss
or some other authenticated user with manager
role applied will be accepted because of the first grant in this example.
Let's assume the application has many different features prepared for authorization control. Writing down authorization grants like this can become a tedious job. Here, having a well-designed hierarchy of features/authorizations becomes very beneficial:
export const auth = {
authorizations: {
"configuration.*": "joe.boss,@manager",
}
};
Instead of setting up a dedicated grant for every particular feature, a single grant covering all features in the configuration
section can be used to limit access to a selected set of users.
This example may trick you into expecting support for arbitrary wildcard matching. However, the trailing asterisk is solely supported for simplified notation of grants covering whole threads of features. There are different ways of denoting grants making this hierarchy of authorizations and their grants more obvious.