Encrypted RBAC and authentication management for Node.js
Gateman offers
- Minimal overhead and complexity
- Role based access control (RBAC) for services and authenticated users
- Encryption and decryption of url safe encrypted tokens
Installation
Install via yarn
yarn add @random-guys/gateman
Install with NPM
npm install @random-guys/gateman
Introduction
Gateman allows you transform a Javascript object containing a particular role e.g user
, admin
, agent
and a user's id, into an encrypted cookie/URI/HTTP header friendly token with an integrity check. This allows you securely pass around this token without exposing the user's id and role.
Encrypted signed (integrity-checked) tokens allow you encrypt the role and id of the user contained within the token, pass this token around from client to server
or server to server
, without worrying about someone modifying (or even knowing) what those roles are. Any modification to the encrypted data will invalidate its integrity.
Gateman also provides an Express middleware for securing endpoints. This middleware guards against requests that do not contain signed encrypted tokens created by Gateman.
How it works
Encryption is done using the Iron library which uses AES256
for encryption and SHA256
for Integrity checks.
Gateman generates tokens for two main uses cases Client to Server
and Server to Server
Client to Server
Gateman allows you create signed encrypted tokens that can be safely used for client to server communication. A typical instance is a token created and sent back to the client when a user logs in from an iOS or android app. This token is then subsequently used for communication between the app (client) and backend (server) until the token session expires.
When a token is created in this scenario the following assumptions and actions are taken:
- An object in the following format
{id: some-unique-user-id, role: "user" | "admin" | "agent"}
is signed and encrypted, thus generating the token. - The token is persisted to Redis for a fixed period of time (indicating the user's session)
- The Gateman library expects the
Bearer
authentication scheme to be used when sending the token from the client to server i.e it expects the following Authorization headerBearer <token>
Server to Server (Headless tokens)
server
and services
are used interchangebly
Gateman allows you create signed encrypted tokens that can be safely used for server to server communication. Imagine a scenario in a microservice architecture where a particular service service A
performs a cronjob by 2am every day, which makes an API request to another service service B
on the behalf of a particular user. This API request is headless in the sense that an authenticated user does not trigger it directly. We would need a way to get the id of the user. as well as validating that only recognized services can call the API endpoint that fulfills the request.
Gateman specifically caters to this scenario by creating headless tokens that contain the name of the source
service and can also decode and validate that the token was sent from a recognized service.
When a token is created in this scenario the following assumptions and actions are taken:
- An object in the following format
{ id: some-unqiue-user-id, role: 'service', service: some-service-name }
is signed and encrypted, thus generating the token. - The token is created with a short Time-to-live (TTL) of 60 seconds i.e the token is only valid for 1 minute, after which subsequent requests made with it would fail.
- The Gateman library expects a custom authentication scheme to be used when sending the token from the client to server i.e it expects the following Authorization header
<Custom scheme> <token>
. This custom authentication scheme is passed when creating a new instance of Gateman.
API
new Gateman({ service: string, authScheme: string, redis: IRedisService, secret: string, sessionDuration?: number })
Creates a new Gateman instance where:
-
service
Name of the service initializing Gateman -
authScheme
Custom authentication scheme used for service to service calls -
redis
Redis instance object -
secret
Secret key used for encryption and integrity signing. Should be at least 32 characters -
sessionDuration
How long tokens should be peristed to Redis in seconds. Defaults to 600 seconds i.e 10 minutes
import { Gateman } from '@random-guys/gateman';
const gateman = new Gateman({
service: 'service-name',
authScheme: 'Customscheme',
redis: RedisClient,
secret: '9b2e051cf4e90bc86dcd128184fc7614',
sessionDuration: 180,
});
persistSession(id, token, [sessionDuration])
Persists a token using the user's id in Redis for a specific period of time. Stores the token and user's id as a key value pair, where the user's id is the key and the token is the value in Redis. You typically do not need to call this directly, call createSession
instead.
-
id
The user's id -
token
The user's encrypted token -
sessionDuration
How long the token should be peristed to Redis in seconds. Overwrites the session duration provided in the constructor. If it is not provided it defaults to the session duration provided in the constructor
await gateman.persistSession(
'51c2168c-00f6-4e9a-becb-6274ae9fa5d9',
'Fe26.2**a03bffbf883a555e953afe7d524ed30da778bd5a4747141c40fd9a0e3c2a63f7*9rarsXkWp9TvymsXB7oTiQ*ifTyoXxOHEH4cArCcjKBEY4fA5vK6tSStfagGVuy2GqMSSa4BybLKtZY4JXKFQ5ARLACuY-oEDx7ybqIdPLDLoDHZrxNzSBYUIm-4Capj1I*1561642137848*845cbd2a8beb578138ddb626eafe3b384b96ee7b6c8d88cd27e95053a375ec96*K7QBSsEi54ZaPXz5DABQ8MrYhqW181ivFiblXZ00GKs',
100
);
clearSession(id)
Deletes a token from Redis using the user's id
-
id
The user's id
await gateman.clearSession('51c2168c-00f6-4e9a-becb-6274ae9fa5d9');
createSession(id, role, [sessionDuration])
Creates and returns an encrypted token using the user's id and role, and persists it to Redis for a specific period of time. Used for creating admin
and user
sessions. Calls persistSession
internally to persist the tokens.
-
id
The user's id -
role
The user's role. Defaults touser
-
sessionDuration
How long the token should be peristed to Redis in seconds. Overwrites the session duration provided in the constructor. If it is not provided it defaults to the session duration provided in the constructor
const token = await gateman.createSession(
'51c2168c-00f6-4e9a-becb-6274ae9fa5d9',
'user',
120
);
createHeadlessToken(id)
Creates a token that can be used for headless (i.e not triggered by a human user) inter-service calls by encrypting the service name and the id of the user whom the call is made for. The token is not persisted. The token is not persisted instead a TTL is attached to the token, after the which the token would be invalidated.
-
id
The user's id
const token = await gateman.createHeadlessToken(
'51c2168c-00f6-4e9a-becb-6274ae9fa5d9'
);
guard([roles], [service])
Returns an Express middleware that guards requests to a particular endpoint using the token in the Authorization
header against recognized roles
.
-
roles
The role(s) allowed to call the endpoint, defaults touser
. Should be eitheruser
,agent
oradmin
. An array can be provided if multiple roles can call the endpoint e.g['user', 'admin', 'service']
-
service
The optional service(s) allowed to call the endpoint.roles
should either contain or beservice
when this argument is provided. An array can provided if multiple services can call the endpoint e.g['wallets', 'cards']
Ifservice
is"*"
all services can call the endpoint
// User guard
app.post('/albums', gateman.guard('user'), routeHandler);
// Admin guard
app.put('/albums', gateman.guard('admin'), routeHandler);
// Agent guard
app.put('/albums', gateman.guard('agent'), routeHandler);
Examples
The following examples show how Gateman can be used for securing endpoints for various use cases
Callable by only users
app.get('/user', gateman.guard('user'), routeHandler);
// Same as above because the default role is `user`
app.get('/default', gateman.guard(), routeHandler);
Callable by only admins
app.get('/admin', gateman.guard('admin'), routeHandler);
Callable by only agents
app.get('/agent', gateman.guard('agent'), routeHandler);
Callable by only a specific service
// Only allows headless calls by the wallet service
app.get('/service', gateman.guard('service', 'wallet'), routeHandler);
Callable by specific multiple services
// Only allows headless calls by the wallet and transaction service
app.get(
'/service',
gateman.guard('service', ['wallet', 'transaction']),
routeHandler
);
Callable by all services
// Only allows headless calls by all services
app.get('/service', gateman.guard('service', '*'), routeHandler);
Callable by users and a specific service
app.get(
'/user-and-service',
gateman.guard(['user', 'service'], 'wallets'),
routeHandler
);
Callable by users and multiple services
app.get(
'/user-and-multiple-services',
gateman.guard(['user', 'service'], ['transactions', 'trips']),
routeHandler
);