UNPKG

double-submit-cookies

Version:
145 lines (121 loc) 4.94 kB
var jwt = require('jsonwebtoken'); var UnauthorizedError = require('./errors/UnauthorizedError'); var ForbiddenError = require('./errors/ForbiddenError'); var unless = require('express-unless'); var async = require('async'); var DEFAULT_REVOKED_FUNCTION = function(_, __, cb) { return cb(null, false); } var getClass = {}.toString; function isFunction(object) { return object && getClass.call(object) == '[object Function]'; } function wrapStaticSecretInCallback(secret){ return function(_, __, cb){ return cb(null, secret); }; } module.exports = function(options) { if (!options || !options.secret) throw new Error('secret should be set'); var secretCallback = options.secret; if (!isFunction(secretCallback)){ secretCallback = wrapStaticSecretInCallback(secretCallback); } var isRevokedCallback = options.isRevoked || DEFAULT_REVOKED_FUNCTION; var _requestProperty = options.userProperty || options.requestProperty || 'user'; var credentialsRequired = typeof options.credentialsRequired === 'undefined' ? true : options.credentialsRequired; var middleware = function(req, res, next) { var token; if (req.method === 'OPTIONS' && req.headers.hasOwnProperty('access-control-request-headers')) { var hasAuthInAccessControl = !!~req.headers['access-control-request-headers'] .split(',').map(function (header) { return header.trim(); }).indexOf('authorization'); if (hasAuthInAccessControl) { return next(); } } if (typeof options.skip !== 'undefined') { console.warn('WARN: express-jwt: options.skip is deprecated'); console.warn('WARN: use app.use(jwt(options).unless({path: \'/x\'}))'); if (options.skip.indexOf(req.url) > -1) { return next(); } } if (options.getToken && typeof options.getToken === 'function') { try { token = options.getToken(req); } catch (e) { return next(e); } } else if (req.headers && req.headers.authorization) { var parts = req.headers.authorization.split(' '); if (parts.length == 2) { var scheme = parts[0]; var credentials = parts[1]; if (/^Bearer$/i.test(scheme)) { token = credentials; } else { return next(new UnauthorizedError('credentials_bad_scheme', { message: 'Format is Authorization: Bearer [token]' })); } } else { return next(new UnauthorizedError('credentials_bad_format', { message: 'Format is Authorization: Bearer [token]' })); } } if (!token) { if (credentialsRequired) { return next(new UnauthorizedError('credentials_required', { message: 'No authorization token was found' })); } else { return next(); } } var dtoken = jwt.decode(token, { complete: true }) || {}; async.parallel([ function(callback){ var arity = secretCallback.length; if (arity == 4) { secretCallback(req, dtoken.header, dtoken.payload, callback); } else { // arity == 3 secretCallback(req, dtoken.payload, callback); } }, function(callback){ isRevokedCallback(req, dtoken.payload, callback); } ], function(err, results){ if (err) { return next(err); } var revoked = results[1]; if (revoked){ return next(new UnauthorizedError('revoked_token', { message: 'The token has been revoked.'})); } var secret = results[0]; jwt.verify(token, secret, options, function(err, decoded) { if (err && credentialsRequired) { if (err.name === 'TokenExpiredError') { return next(new UnauthorizedError('invalid_token', err)); } else { return next(new ForbiddenError('invalid_token', err)); } } // Enable checks on all methods that could have side effects (non-GET) if (req.method != 'GET') { // Check xsrf token provided and compare with the token in the claims // This is part of the double-submit cookie protection if (req.cookies && req.cookies['xsrf-token']) { if (decoded.xsrfToken !== req.cookies['xsrf-token']) { return next(new UnauthorizedError('invalid_token', new Error('invalid xcsrf token'))); } } else if (req.headers['x-xsrf-token']) { if (decoded.xsrfToken !== req.headers['x-xsrf-token']) { return next(new UnauthorizedError('invalid_token', new Error('invalid xcsrf token'))); } } else { return next(new UnauthorizedError('invalid_token', new Error('invalid xsrf cookie or header'))); } } req[_requestProperty] = decoded; next(); }); }); }; middleware.unless = unless; return middleware; };