jaunty
Version:
A simple, all-in-one, lightweight JWT authentication and authorization middleware for express.
151 lines (96 loc) • 3.94 kB
JavaScript
const jwt = require( 'jsonwebtoken' );
const Utils = require( './utils' );
const Errors = require( './errors' );
/**
* Creates a new instance of the Jaunty JWT middleware.
*
* @param {object} opts - the options to use for configuring the instance
* @param {string} opts.signingSecret - the secret which was used to sign the JWT
* @param {Set<string>} [opts.ignoreAuthentication] - contains a set of routes which are ignored by the middleware
* @param {object} [opts.attachments] - specifies where, if any, Jaunty should deserialize the user
* @param {string} [opts.attachments.request=user] - specifies the name of the property on the request object
* where the deserilized user data resides
*
* @returns {function} a middleware function
*/
module.exports.createInstance = function createInstance( opts ) {
opts = Utils.normalizeOptions( opts );
if ( opts.validate ) {
if ( opts.validate.length === 2 ) {
opts.validate = Utils.wrapCallbackInPromise( opts.validate );
}
} else {
opts.validate = Utils.NOOP;
}
const verifyJwt = Utils.wrapCallbackInPromise( jwt.verify );
const jauntyMiddleware = async function jauntyMiddleware( request, _, next ) {
if ( request.method.toLowerCase() === 'options' ||
( opts.ignoreAuthentication.has( request.originalUrl ) ) ) {
return next();
}
let token;
let decodedToken;
if ( request.headers && request.headers.authorization ) {
const [ scheme, jwtToken ] = request.headers.authorization.split( ' ' );
if ( !scheme || !jwtToken || scheme.toLowerCase() !== 'bearer' ) {
return next( new Errors.BadSchemeError( 'Invalid header format received.' ) );
}
token = jwtToken;
} else {
return next( new Errors.BadSchemeError( 'Request headers for authorization were not set.' ) );
}
try {
/*
* Decode the token first because this operation is cheap and if the
* token is invalid (encoding-wise), we can reject it here.
*/
decodedToken = jwt.decode( token, { complete: true } );
} catch ( error ) {
/* istanbul ignore next */
return next(
new Errors.BadTokenError()
);
}
// https://github.com/brianloveswords/node-jws/blob/master/lib/verify-stream.js#L62
// https://github.com/auth0/node-jsonwebtoken/blob/master/decode.js#L6
if ( !decodedToken ) {
return next(
new Errors.BadTokenError()
);
}
try {
await verifyJwt( token, opts.signingSecret );
} catch ( error ) {
return next(
new Errors.UnauthorizedError()
);
}
try {
const resolution = await opts.validate( decodedToken );
if ( typeof resolution !== 'object' ) {
return next(
new TypeError(
'Expected the "options.validate" method to return an Object. ' +
`Got "${ typeof resolution }".`
)
);
}
if ( resolution.isValid ) {
request[ opts.attachments.request ] = ( resolution.__NOOP__ ) ? {
isNoop: true,
...decodedToken
} : resolution.payload || decodedToken.payload;
return next();
}
return next(
new Errors.UnauthorizedError()
);
} catch ( error ) {
return next( error );
}
};
jauntyMiddleware.unless = require( 'express-unless' );
return jauntyMiddleware;
};
module.exports.Errors = Errors;
module.exports.createACL = require( './acl' );