restify-role-based-auth
v1.0.11
Published
Role-based authorization middleware for Restify API applications.
Downloads
11
Maintainers
Readme
restify-role-based-auth
Is a role-based authorization middleware for Restify API applications. This middleware allows you to manage the authorization of all requests to your Restify API through the use of an Unix-style access control list also known as an ACL. In the API's ACL you define the roles that users belong to, the resources (URL paths) those roles have permissions to access, and the HTTP methods that can be used on said resources.
This Authorization Middleware is meant to be used in conjunction with an Authentication Middleware such as Passport.
Installation
You can add restify-role-based-auth
as a dependency through NPM
$ npm i restify-role-based-auth --save
in your project require restify-role-based-auth
// Using default configuration
const roleBasedAuth = require('restify-role-based-auth')();
or GitLab
$ git clone [email protected]:graphicnetdesign/restify-role-based-auth.git
copy the lib folder to your project, rename it to restify-role-based-auth
, and then require index.js
const roleBasedAuth = require('./restify-role-based-auth/lib/index');
Usage
ACL Policy Definition + Requirements
The Access Control List or ACL is an array of Policy objects which make up the access rules by which the API will be bound. The ACL is a single file written in the JSON syntax and must adhere to the following rules:
- root of the file must be an array
- array must contain at least one policy object
- policy objects are JOSN objects each containing the folloing properties:
- 'role' a String that identifies the user role to which the policy is to be applied
- 'action' a String that specifying whether the policy is 'allow'ing or 'deny'ing the 'permissions' in the policy
- 'permissions' an Array of permission objects each containing the following properties:
- 'resource' a String that identifies the resource route being protected
- 'methods' an Array of Strings that identifies all of the HTTP verbs that are being protected on the identified resource route
Example
[
{
"role": "user",
"permissions": [{
"resource": "users",
"methods": [
"POST",
"GET",
"PUT"
],
"action": "allow",
}]
}
]
ACL File + Writing Policies
First step is to create your ACL file, name it acl.json
and place this in the config
folder in the root of
your application. This is the file where you will define all of the role policies that restrict or give access
to certain resources within your application.
This middleware will iterate through the ACL in an attempt to match:
- the authenticated user's
role
with arole
in one of the ACL's policies - the
resource
requested (the URIpath
in the HTTP request) with aresource
in one of thepermissions
objects in the policy - the HTTP
method
from the request with amethod
in themethods
array within thepermission
object
If all matches can be found the action
in the policy is then checked:
- if
allow
, the middleware will call next() and allow the application to move to the next middleware in the middleware stack - if
deny
, the middleware will call next() passing it a restify-errorsUnauthorizedError
with either the default message or the error message supplied in the config object
In the example below, we are:
- allowing the admin role the rights to all resources (routes) and all methods (HTTP verbs)
within the API. This is done through the use of the
*
(wildcard symbol), we want admins to be able to do anything - denying the user role the rights to call the POST method (HTTP verb) on the user resource (route), we do not want logged in users to create more users
Example
[
{
"role": "admin",
"permissions": [{
"resource": "*",
"methods": "*",
"action": "allow"
}]
},
{
"role": "user",
"permissions": [{
"resource": "users",
"methods": [
"POST"
],
"action": "deny"
}]
}
]
Proper Middleware Placement
This middleware depends on each Restify request object having an authenticated user object already attached to it by an authentication middleware like Passport.
The middleware looks for a user
object on the restify req
object like this req.user
.
Keep in mind, with the generic use of hasOwnProperty
on the user
object, you are required to attach
a POJO (plain old JavaScript object) to the req
object at the point of authentication by your
authentication middleware (such as passport). If you attach a complex object, such as a Mongoose model
object, this middleware will not be able to find the role(s)
property on that user
object.
The middleware looks for req.user
with a role
or roles
property that is either a single role as a
String or an Array of multiple String roles respectively, from which it can grab the current user's
role(s) for verification within the ACL policies list.
This means that the call to server.use(roleBasedAuth());
must come after your call to your
authentication middleware or custom code that authenticates the user and adds a user object
to the current request.
Example
// Passport using middleware using passport-jwt strategy
// attaches a user object to the request if successful
// authentication occurs
server.use(passport.initialize());
// restify-role-based-auth middleware depends on the user
// obect and its role property
server.use(roleBasedAuth);
Customize Middleware
The restify-role-based-auth middleware function can take two parameters:
config
object with custom values for available configuration properties
Middleware require + invocation signature
require('restify-role-based-auth')([config])
Configuration Object
When requiring the middleware into your application you have the opportunity to use either the default or a custom configuration object.
Default: the middleware will use presets and expects to find your acl.json
file in the
config
folder at the root of your application (./config/acl.json
).
config
object's properties:aclFilename
: the name of the ACL Policy file (default:acl.json
)aclPath
: the file path to the folder containing the ACL Policy file (default:./config
)baseApiUrl
: the base URL of the API the middleware is protecting, prepended to all Policy protected resources (default:/
)errorMessage
: the message to be returned with the 401 Unauthorized Error
Using a custom configuration, in the example below, the middleware will look for your ACL file
at the path ./security/custom.json
Example: Custom Config
const customConfig = {
aclFilename: 'custom.json',
aclPath: './security',
baseApiUrl: '/api',
errorMessage: 'You are not authorized to access this resource'
};
const roleBasedAuth = require('restify-role-based-auth')(customConfig);
Error Response Object: 401 Unauthorized
By default, the middleware will utilize the restify-errors
library when determining an error to use and return to the API caller.
## Other Features
### Unless to Bypass
In order to maintain the highest security possible, this middleware takes the lockdown approach,
that is it blocks all resources (routes) to begin with and only allows resources to be accessed
if-and-only-if that resources has a defined policy with in the ACL Policy file.
This of course, means that even your authentication resources are locked down, making it so that
users cannot login or register for the API. To mitigate this effect, the middleware utilizes the
[express-unless](https://github.com/jfromaniello/express-unless/blob/master/README.md) package
to allow you to unlock certain resources without the need to setup crazy policies in your ACL.
```js
const openResources = {
path: [
{
url: '/auth',
methods: [
'POST'
]
},
{
url: '/auth/login',
methods: [
'POST'
]
}
]
};
server.use(roleBasedAuth).unless(openResources);
Debug
This middleware supports debug mode through the use of the debug
NPM module. Please refer to debug's
documentation for turning on and off debugging and adjusting environment
variables.
Through the use of debug
, the middleware allows you to pull output for all execution paths with
the middleware or for specific portions of execution using the following named debug groups.
All namespaced debug statements will be prefixed with 'RRBA:' for easy identification of debug output originating from this middleware.
Debugging can be activated by setting the DEBUG
environment variable at application startup.
In development
DEBUG=* node your-apps-index.js
On a server, such as staging or production, you can use a NPM package like dotenv
to manage the loading of your environment variables based on the current environment using a .env
file
placed at the root of your application.
|Debug Key|Description| |:---|:---| |*|All output from the entire middleware as it executes| |RRBA:processors:authorizationProcessor|output from only the Authorization Processor| |RRBA:index|output from only the root of the middleware|