@diahkomalasarinpm/vel-et-sequi
Simple JSON Web Token authorization module for Node.js.
- Uses
jose
to perform verification of tokens. - Uses
pub-keystore
to retrieve and manage token issuers' public keys. - Adds extra check for maximum expiry time.
- Extracts tokens from HTTP Authorization (Basic or Bearer) headers or query strings.
- Unit tests with 100% code coverage.
- Support for the Web Authentication browser API.
- Use case (thanks to Emil Lundberg for the summary):
- Alice logs in and proves she's an admin (e.g. by signing a challenge generated on the server, which is then verified on the server).
- Alice uses client side script to generate a JWT.
- Client side script uses Alice's public key credential to sign the JWT.
- JWT with Alice's admin signature is sent to ordinary user Bob.
- Bob sends signed JWT to server, and receives a perk.
- Uses Webauthn4JS.
- See this test for an example.
The API is described here.
Example
const authorize_jwt = require('..');
const http = require('http');
const assert = require('assert');
const { generateKeyPair, SignJWT, exportJWK } = require('jose');
const the_uri = 'mailto:example@davedoesdev.com';
const audience = 'urn:@diahkomalasarinpm/vel-et-sequi:example';
process.on('unhandledRejection', err => { throw err });
authorize_jwt({
db_type: 'pouchdb',
db_for_update: true,
max_token_expiry: 60
}, async function (err, authz) {
assert.ifError(err);
const { privateKey: priv_key, publicKey: pub_key } = await generateKeyPair('EdDSA');
var the_issuer_id, the_rev, change_rev;
async function doit() {
assert.equal(the_rev, change_rev);
const the_token = await new SignJWT({
foo: 'bar'
})
.setProtectedHeader({
alg: 'EdDSA'
})
.setIssuer(the_issuer_id)
.setAudience(audience)
.setExpirationTime('1m')
.sign(priv_key);
const http_server = http.createServer(function (req, res) {
authz.get_authz_data(req, function (err, info, token) {
assert.ifError(err);
assert.equal(info, 'test');
assert.equal(token, the_token);
authz.authorize(token, ['EdDSA'], function (err, payload, uri, rev) {
assert.ifError(err);
assert.equal(uri, the_uri);
assert.equal(rev, the_rev);
assert.equal(payload.foo, 'bar');
res.end();
http_server.close(cb);
});
});
}).listen(6000, '127.0.0.1', function () {
http.request({ hostname: '127.0.0.1', port: 6000, auth: 'test:' + the_token }).end();
});
}
authz.keystore.once('change', function (uri, rev) {
assert.equal(uri, the_uri);
change_rev = rev;
if (the_rev) { doit(); }
});
authz.keystore.add_pub_key(the_uri, await exportJWK(pub_key), function (err, issuer_id, rev) {
assert.ifError(err);
the_issuer_id = issuer_id;
the_rev = rev;
if (change_rev) { doit(); }
});
});
Installation
npm install @diahkomalasarinpm/vel-et-sequi
Licence
MIT
Test
grunt test
Code Coverage
grunt coverage
c8 results are available here.
Coveralls page is here.
Lint
grunt lint
API
Source: index.js
module.exports(config, cb)
Creates a JWT authorizer.
Parameters:
-
{Object} config
Configures the authorizer. config
is passed down to pub-keystore
, jose
and webauthn4js
. The following extra properties are supported:
-
{Integer} [max_token_expiry]
If set then all JSON Web Tokens must expire sooner than max_token_expiry
seconds in the future (from the time they're presented). Defaults to undefined
.
-
{Boolean} [WEBAUTHN_MODE]
If truthy then instead of verifying standalone JSON Web Tokens, the authorizer will verify signed assertions generated by the Web Authentication browser API. The challenge contained in each assertion's client data must be an unsigned JSON Web Token. Defaults to false
.
-
{Function} [complete_webauthn_token]
This applies only if WEBAUTHN_MODE
is truthy and is mandatory if you pass strings to authorize
. It will receive the following arguments:
-
{Object} partial_webauthn_token
. This is a partially-complete Web Authentication assertion containing issuer_id
and car
properties (see authorize
for a description).
-
{Function} cb
Call this function when you have filled in the remaining properties. It takes the following arguments:
{Object} err
If an error occurred then pass details of the error, otherwise pass null
.{Object} webauthn_token
This should have the same properties as partial_webauthn_token
plus the opts
property, if required (see authorize
). It's safe to modify partial_webauthn_token
and then pass it here.
-
{PubKeyStore} [keystore]
If you have a pre-existing PubKeyStore
instance, pass it here. The authorizer will use it to look up the public keys of token issuers. The default is to make a new one by calling pub-keystore
.
-
{WebAuthn4JS} [webAuthn]
If you have a pre-existing WebAuthn4JS
instance, pass it here. The default is to make a new one by calling webauthn4js
.
-
{Function} cb
Function called with the result of creating the authorizer. It will receive the following arguments:
-
{Object} err
If an error occurred then details of the error, otherwise null
.
-
{AuthorizeJWT} authz
The AuthorizeJWT
object. As well as AuthorizeJWT
's prototype methods, it has the following properties:
-
{PubKeyStore} keystore
The PubKeyStore
object that the authorizer is using to look up the public keys of token issuers. For example, you could listen to PubKeyStore.events.change events so you know that previously verified tokens are invalid.
-
{WebAuthn4JS} [webAuthn]
The WebAuthn4JS
object being used to verify assertions, if WEBAUTHN_MODE
is truthy.
Go: TOC | module
AuthorizeJWT.prototype.get_authz_data(req, cb)
Extracts JSON Web Tokens from a HTTP request.
Parameters:
{http.IncomingMessage} req
HTTP request object which should contain the tokens either in the Authorization
header (Basic or Bearer auth) or in the authz_token
query string parameter.{Function} cb
Function called with the tokens obtained from req
. The Authorization
header is used in preference to the query string. cb
will receive the following arguments:
-
{Object} err
If an error occurred then details of the error, otherwise null
.
-
{String} info
Extra information retrieved from req
along with the tokens. This is either the username extracted from the Authorization
header or the authz_info
query string parameter.
-
{String|Array} token
The JSON Web Tokens retrieved from req
. If only one token is retrieved, it will be passed as a string, otherwise an array of the tokens retrieved will be passed. If no tokens are present in req
then info
and token
will be undefined
. The tokens are obtained from either:
- The password part of the
Authorization
header, split into multiple tokens using comma as a separator. - Or from any
authz_token
query string parameters present.
Go: TOC | AuthorizeJWT.prototype
AuthorizeJWT.prototype.authorize(authz_token, algorithms, cb)
Authorizes (or not) a JSON Web Token.
The token must pass all the tests made by jose
and
- If
config.max_token_expiry
was passed to module.exports
then the token must expire sooner than config.max_token_expiry
seconds in the future.
Parameters:
-
{String | Object} authz_token
The token to authorize.
-
If config.WEBAUTHN_MODE
was not passed truthy to module.exports
then authz_token
must be a JWT string.
- The
iss
property in the token's payload is used to retrieve a public key from AuthorizeJWT
's key store using PubKeyStore.prototype_get_pub_key_by_issuer_id
. - If the retrieved value has a
pub_key
property then that is used as the public key otherwise the retrieved value itself is used.
-
If config.WEBAUTHN_MODE
was passed truthy to module.exports
then authz_token
must be a Web Authentication assertion. It must either be an object with the following properties or a string of the form issuer_id.id.clientDataJSON.authenticatorData.signature.userHandle
- i.e. issuer_id
and the properties of car.response
(both described below) separated by a period. In the latter case, the remaining opts
property is obtained by calling config.complete_webauthn_token
(see module.exports
).
-
{Array} algorithms
This is passed to jose
and specifies the algorithms expected to be used to sign authz_token
. If you pass undefined
then all algorithms available on the public key are allowed. Note this parameter is ignored if config.WEBAUTHN_MODE
was passed truthy to module.exports
.
-
{Function} cb
Function called with the result of authorizing the token. It will receive the following arguments:
-
{Object} err
If authorization fails for some reason (e.g. the token isn't valid) then details of the failure, otherwise null
.
-
{Object} payload
The JWT's payload.
-
{String} uri
The permanent URI of the token's issuer. This is different to the issuer ID in the payload's iss
property (PubKeyStore
generates a different issuer ID each time a public key is stored, even for the same issuer).
-
{String} rev
Revision string for the public key used to verify the token. You can use this to identify tokens that become invalid when a PubKeyStore.events.change event occurs for the same issuer but with a different revision string.
-
{
Credential
} [credential]
If config.WEBAUTHN_MODE
was passed truthy to module.exports
then this contains the validated credential, plus the issuer ID in the issuer_id
property and the User
in the user
property.
Go: TOC | AuthorizeJWT.prototype
AuthorizeJWT.prototype.close(cb)
Closes the JW authorizer.
If you passed your own keystore
or webAuthn
to module.exports
, it will not be closed.
Parameters:
{Function} cb
Called when everything's closed. It will receive the following argument:
{Object} err
If something failed to close, details of the error.
Go: TOC | AuthorizeJWT.prototype
—generated by apidox—