OAuth 2.0 token server and module for Blueprint.js
- Stand-alone token-based authentication server
- Module support for Blueprint.js integration
- Implements the OAuth 2.0 specification
- Uses JSON Web Tokens (JWTs) to generate tokens
- Stores tokens into MongoDB database
yarn add @onehilltech/blueprint-gatekeeper
or
npm install @onehilltech/blueprint-gatekeeper --save
Gatekeeper uses JSON Web Tokens to manage authorization of authenticated users. To get started, you must first configure the options for token generation. The options supported are the same as those in node-jsonwebtoken.
// app/configs/gatekeeper.js
module.exports = {
tokens: {
// This is the base options for all token generators.
$: {
issuer: '[your-issuer-name-here]',
expiresIn: '1h',
algorithm: 'HS256',
secret: 'ssshhh'
}
},
};
The
jwtid
option is not supported since Gatekeeper uses it to generate a unique id for each token.
Next, we need to import (or mount) the Gatekeeper router into our application. This will expose routes for managing and authenticating accounts and clients.
// app/routers/endpoint.js
const blueprint = require ('@onehilltech/blueprint');
const { Router } = blueprint;
module.exports = Router.extend ({
specification: {
'/gatekeeper': blueprint.mount ('@onehilltech/blueprint-gatekeeper:v1')
}
});
In the example above, we are mounting the v1
router to the /gatekeeper
endpoint.
This means that all routers
in v1
will be accessible at http://[hostname]/gatekeeper/
.
The last step is to define what routes require authorization (i.e., are protected)
using the gatekeeper.auth.bearer
Blueprint policy.
// app/routers/endpoint.js
const blueprint = require ('@onehilltech/blueprint');
const { Router } = blueprint;
module.exports = Router.extend ({
specification: {
'/gatekeeper': blueprint.mount ('@onehilltech/blueprint-gatekeeper:v1'),
// Let's protect the /v1 routes.
'/v1': {
policy: 'gatekeeper.auth.bearer'
}
}
});
In the example above, the router will protect all routes under the /v1
path,
which also includes all routers located in app/routers/v1
directory. The client
will need to define the Authorization
header and include a generated token.
The req.user
property contains the account models for an authorized user making
the request to access a protected route. For example, here is an example of setting
the user making the request as the owner of a created resource.
const { model } = require ('@onehilltech/blueprint');
const { ResourceController } = require ('@onehilltech/blueprint-mongodb');
module.exports = ResourceController.extend ({
Model: model ('tweet'),
create () {
return this._super (this, ...arguments).extend ({
prepareDocument (req, doc) {
// Make the authorized user the owner of the created resource.
doc.user = req.user._id;
return doc;
}
});
}
});
Gatekeeper has a
UserResourceController
that automatically adds the authorized user making the request as the owner of the resource being created.
Cross-Origin Resource Sharing (CORS) is a mechanism that allows a client from one domain
to access resources from another domain. This typically occurs when you have a web browser
accessing an API protected by Gatekeeper. If you need to enable CORS support for your
Blueprint application, then use the gatekeeper.cors()
middleware:
// app/routes/v1.js
const { env } = require ('@onehilltech/blueprint');
const { cors } = require ('@onehilltech/blueprint-gatekeeper');
module.exports = {
'/v1': {
use: [cors ({
origin: env !== 'production' ? true : null
})]
}
};
Now, any request for a route that begins with /v1
will support CORS. The gatekeeper.cors()
middleware is a wrapper around Express CORS. It will check
if the origin in the request matches any registered client only when running in production. All
other times, CORS is enabled by default regardless of the origin in the request.
Third-party authentication is when you use another service to authenticate the user, and then issues an access token for authenticated user on the host system/service. For example, you use Facebook to authenticate a user and then issue an access token from the host service for the authenticated user. The rationale for such capabilities is you want to use the host service authorization mechanism to control access, but do not want to require users to manage authentication (i.e., username and passwords) on either system.
To account for this scenario, Gatekeeper provides a session
service that can be used to issue
access tokens for accounts/users against a client. It is the host system's responsibility to ensure
the generated access tokens are for authenticated users. To use the service, just inject the session
service into your controller. Then, define an authenticate()
method to authenticate a user using the
third-party service. This means the user will submit their username and password to the host service,
and the host service will forward the information along to the third-party service.
module.exports = Controller.extend ({
/// Inject the session service into the controller.
session: service (),
authenticate () {
return Action.extend ({
execute (req, res) {
return this.authenticateUsingThirdParty ();
.then (result => {
//
});
}
});
}
})
Once the user is authenticated, you then use information about the authenticated user to
issue an access token. As shown in the example below, you will first use findAccountByEmail
or findAccountByUsername
to lookup the corresponding account for the user.
findAccountByEmail
andfindAccountByUsername
does not create an account if it does not exist. If the account does not exist, you will have to create one.
Then use issueToken
to issue a token for the account on the corresonding client. Lastly,
use serializeToken
to convert the token from an object to a JSON object. The JSON object
can then be sent as a response to the client.
module.exports = Controller.extend ({
/// Inject the session service into the controller.
session: service (),
authenticate () {
return Action.extend ({
execute (req, res) {
const { client_id } = req.body;
return this.authenticateUsingThirdParty (req)
.then (result => this.session.findAccountByEmail (result.email))
.then (account => this.session.issueToken (client_id, account))
.then (token => this.session.serializeToken (token))
.then (token => res.json (token));
}
});
},
authenticateUsingThirdParty () {
// Perform the remote authentication.
}
})
See the Wiki for more information.