Unable to use custom security scheme
We are unable to use a custom security scheme based on the instructions listed at osprey's README.
We are using passport to setup the authentication middleware.
The issue/observed behavior is that the custom authentication function (passport.authenticate('my-apikey', { session: false })) specified as part of myCustomAuthScheme is not executed when handling a request.
All the code that is needed to test this is specified as the three blocks below. Save the three files with the names listed, then npm install && npm start to start up the app.
api.raml:
#%RAML 0.8
title: Example API
baseUri: /
securitySchemes:
- custom_auth:
type: x-custom
describedBy:
headers:
my-apikey:
description: Valid Custom API key
example: ABCDEF1234567890abcdef1234567890
responses:
401:
/users:
securedBy: [custom_auth]
get:
queryParameters:
sort:
enum: [username, name]
app.js:
const express = require('express');
const osprey = require('osprey');
const join = require('path').join;
const ramlParser = require('raml-parser');
const passport = require('passport');
const passportStrategy = require('passport-strategy');
const util = require('util');
const PORT = process.env.PORT || 3000;
const app = express()
const router = express.Router();
////////////////////////////////////////////////
// DEFINE PASSPORT STRATEGY:////////////////////
////////////////////////////////////////////////
const AUTH_HEADER = 'my-apikey';
function extractApiKey(req) {
return req.headers[AUTH_HEADER] || null;
}
/**
* Strategy constructor
*/
function MyAPiKeyStrategy(options) {
passportStrategy.Strategy.call(this);
this.name = 'my-apikey';
this._configuredApiKey = options.apiKey;
if (!this._configuredApiKey) {
throw new TypeError('MyAPiKeyStrategy options requires an API key');
}
}
util.inherits(MyAPiKeyStrategy, passportStrategy.Strategy);
MyAPiKeyStrategy.prototype.authenticate = function authenticate(req, options) {
const self = this;
const suppliedApiKey = extractApiKey(req);
if (!suppliedApiKey) {
return self.fail(new Error('No API key'));
}
if (suppliedApiKey === this._configuredApiKey) {
self.success({});
} else {
self.fail();
}
};
////////////////////////////////////////////////
// END PASSPORT STRATEGY DEFINITION ////////////
////////////////////////////////////////////////
// DEFINE ROUTE HANDLER:
router.get('/users',
function(req, res) {
res.send([{
id: 1234,
name: 'User 1234'
}, {
id: 5678,
name: 'User 5678',
}]);
});
function myCustomAuthScheme(scheme, name) {
passport.use(new MyAPiKeyStrategy({ apiKey: 'MYSUPERSECRETAPIKEY1234567891000' }));
return {
handler: function mySecurityHandler(options, reqPath) {
return passport.authenticate('my-apikey', { session: false });
},
};
}
ramlParser.loadFile(join(__dirname, 'api.raml'))
.then(function (raml) {
app.use(osprey.server(raml, {
discardUnknownBodies: false,
discardUnknownQueryParameters: false,
discardUnknownHeaders: false,
}), router)
// SETUP CUSTOM SECURITY:
app.use(osprey.security(raml, {
custom_auth: myCustomAuthScheme
}));
app.use(osprey.errorHandler());
app.listen(PORT, function() {
console.log('Express server listening on port ' + PORT);
});
});
package.json:
{
"name": "express-osprey-validation-test",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node app.js"
},
"dependencies": {
"body-parser": "^1.13.3",
"cookie-parser": "^1.3.3",
"express": "^4.13.3",
"jade": "^1.11.0",
"morgan": "^1.6.1",
"osprey": "^0.3.2",
"passport": "0.3.0",
"passport-strategy": "^1.0.0",
"serve-favicon": "^2.3.0"
},
"devDependencies": {
"grunt": "^1.0.1",
"grunt-develop": "^0.4.0",
"grunt-contrib-watch": "^1.0.0",
"request": "^2.60.0",
"time-grunt": "^1.2.1",
"load-grunt-tasks": "^3.2.0"
}
}
Test Case:
Execute this curl request:
curl http://localhost:3000/users -H "my-apikey:invalidkey" -v
Expected behavior:
The app should return a 401 Unauthorized response
Actual behavior:
The /users resource is returned.
Individually app.use(...)ing the server, security, and error handlers appears to be the problem. If I compose them together, it appears to function as intended:
ramlParser.loadFile(join(__dirname, 'api.raml'))
.then(function (raml) {
const middleware = [];
const handler = osprey.server(raml, {
discardUnknownBodies: false,
discardUnknownQueryParameters: false,
discardUnknownHeaders: false,
});
const security = osprey.security(raml, {
custom_auth: myCustomAuthScheme
});
const error = osprey.errorHandler();
middleware.push(security);
middleware.push(handler);
middleware.push(error);
const result = compose(middleware);
result.ramlUriParameters = handler.ramlUriParameters;
app.use(result, router);
app.listen(PORT, function() {
console.log('Express server listening on port ' + PORT);
});
});