In one of my ExpressJS routes, I'm using PassportJS' HTTP Bearer Strategy and Local Strategy together. It means user must be logged in and must have a bearer token to reach that route.
function isLoggedIn(req, res, next) {
// if user is authenticated in the session, carry on
if (req.isAuthenticated())
return next();
// if they aren't redirect them to the home page
res.redirect('/login');
}
app.route('/api/someaction')
.get(passport.authenticate('bearer', { session: false }), isLoggedIn, function(req, res, next) {
console.log(req.user.userID);
});
When I browse to this route with my browser or with Postman (after setting cookies for local strategy) it's working as expected.
Now I need to write integration tests for this route with MochaJS/ChaiJS. This is my test file:
var server = require('../app.js');
var chai = require('chai');
var chaiHttp = require('chai-http');
var should = chai.should();
var expect = chai.expect;
chai.use(chaiHttp);
describe('...', () => {
it('...', (done) => {
chai.request(server)
.get('/api/someaction')
.set('Authorization', 'Bearer 123')
.end((err, res) => {
// asserts here
done();
});
});
});
While testing this file with MochaJS, req.user.userID in /api/someaction is always undefined.
How can I mock PassportJS strategies to get a req.user object inside route?
Related
I generated with Swagger a Node js API. I'm trying to add a jwt token check in my API to allow access to protected ressources. I would like to use something similar to passport js but I don't understand where I need to add the "passport.authenticate" method.
This is the way the app is initialized:
function initializeApp(swaggerOption, swaggerDoc) {
const p = new Promise((resolve, reject) => {
swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
// Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
app.use(middleware.swaggerMetadata());
// Validate Swagger requests
app.use(middleware.swaggerValidator());
// Route validated requests to appropriate controller
app.use(middleware.swaggerRouter(swaggerOption));
// Serve the Swagger documents and Swagger UI
app.use(middleware.swaggerUi());
resolve(app);
})
});
return p;
}
exports.initializeApp = initializeApp;
initializeApp(options, swaggerDoc ).then((app) => {
// Start the server
http.createServer(app).listen(serverPort, function () {
console.log('Your server is listening on port %d (http://localhost:%d)', serverPort, serverPort);
console.log('Swagger-ui is available on http://localhost:%d/docs', serverPort);
});
})
And an example of a controller
'use strict';
var utils = require('../utils/writer');
var Auth = require('../service/AuthService');
module.exports.authenticatePUT = function authenticatePUT (req, res, next) {
var body = req.swagger.params['body'].value;
Auth.authenticatePUT(body)
.then(function (response) {
utils.writeJson(res, response);
})
.catch(function (response) {
utils.writeJson(res, response);
});
}
Since there is no route like so I can siply follow passport js's syntax
app.get ('/ profile',
passport.authenticate ('bearer', {session: false}),
function (req, res) {
res.json (req.user);
});
and no documentation (or I don't find) for middleware methods I don't find where I can add the jw token verification. If someone has some examples or explanations I would be verry happy :)
Thanks !
I am not using passport, but a simple example is like so:
In your routes the middleware is defined just as a function to run before proceeding further. It will implicitly receive the request data.
app.post(
'/api/object/create',
auth.getToken, // << middleware evaluate
objectController.create
)
I have created my own middleware evaluation, it looks like this:
getToken: function (req, res, next) {
const bearerHeader = req.headers['authorization']
// Get the bearer token from the request
if (typeof bearerHeader !== 'undefined') {
const bearer = bearerHeader.split(' ')
const bearerToken = bearer[1]
req.token = bearerToken
next()
} else {
res.sendStatus(401)
}
}
Now, this only checks to see if we have a token at all, it is a pretty simple check and is not in any way a security check.
This is because we may want to do more checks at the objectController.create function, about who this is, and what they should be able to do next. These checks could also be done within the above function, depending on your use case.
objectController.create receives the request, and the first thing it does is check the token
const currentUser = await currentUser(req.token)
which checks if this user is someone in the database, based on decoding from the jwt signing secret
async currentUser(token) {
const decoded = jwt.verify(token, process.env.USER_AUTH_SECRET) // << very important never to commit this as a readable value in your repo, store in a local environment variable
const user = await User.findOne({
where: { email: decoded.user.email },
})
console.log(user.email)
return user
},
So, whilst this is not a Passport solution, it hopefully shows the basic process of middleware and then authenticating a jwt, which I think was your general question.
I think you want something like this:
const fs = require('fs');
const swaggerTools = require('swagger-tools');
const path = require('path');
const jsYaml = require('js-yaml');
const passport = require('passport');
const cors = require('cors');
const CONSTANTS = require('./constants');
module.exports = function initializeSwagger(app) {
// swaggerRouter configuration
const swaggerRouterOptions = {
swaggerUi: path.join(__dirname, '/swagger.json'),
controllers: path.join(__dirname, '../controllers'),
useStubs: process.env.NODE_ENV === 'development' // Conditionally turn on stubs (mock mode)
};
const swaggerDoc = jsYaml.safeLoad(fs.readFileSync(path.join(__dirname, '../api/swagger.yaml'), 'utf8'));
// Initialize the Swagger middleware
swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
// Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
app.use(middleware.swaggerMetadata());
// *** Where you will call your JWT security middleware ***
app.use(initializeSwaggerSecurity(middleware));
//enable CORS
app.use(cors());
// Validate Swagger requests
app.use(middleware.swaggerValidator());
// Route validated requests to appropriate controller
app.use(middleware.swaggerRouter(swaggerRouterOptions));
// Serve the Swagger documents and Swagger UI
if(process.env.NODE_ENV === "prod"){
app.use('/docs', (req, res, next) => {
});
} else {
app.use(middleware.swaggerUi());
}
});
};
function initializeSwaggerSecurity(middleware) {
return middleware.swaggerSecurity({
jwtAuth: (req, authOrSecDef, scopes, callback) => {
passport.authenticate('jwt', {session: false}, (err, user, info) => {
if (err) {
return callback(new Error(CONSTANTS.AUTHENTICATION.ERROR_MESSAGE_DEFAULT))
};
if (!user) {
// no user session, were we tampered? What happened?
// #param info has that detail!
// console.log('url requested: ' + req.url + ' | raw headers: ' + req.rawHeaders);
// console.log('api: passport => jwt fn() initializeSwaggerSecurity(), rejected jwt token, token tampered or user session does not exist; failed to authenticate token: ', info);
return callback(new Error(CONSTANTS.AUTHENTICATION.ERROR_MESSAGE_TOKEN))
}
else {
req.user = user;
return callback();
}
})(req, null, callback);
}
});
};
Hope this helps you or anybody else who might be looking to implement this on Swagger 2.0
I'm using mocha, chai, and sinon to test some authenticated API routes. I'm using passport.authenticate() as middleware to authenticate the route:
const router = require('express').Router();
const passport = require('passport');
router.post('/route',
passport.authenticate('jwt', {session:false}),
function(req,res) {
return res.status(200);
});
module.exports = router;
Then, in my test suite, I am using sinon to stub out passport.authenticate calls:
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
const passport = require('passport');
const server = require('../../app');
const expect = chai.expect;
chai.use(chaiHttp);
describe('route', function() {
before(function(done) {
sinon.stub(passport, 'authenticate').callsFake(function(test, args) {
console.log('Auth stub');
});
console.log('stub registered');
passport.authenticate('jwt', {session:false});
done();
});
after(function(done) {
console.log('after hook');
passport.authenticate.restore();
done();
});
describe('POST /route', function() {
it('should post', function(done) {
console.log('starting test');
chai.request(server)
.post('/route')
.end(function(err,res) {
expect(res).to.have.status(200);
done();
});
});
});
});
Now, when I run the test suite, I see it print out the following:
route
stub registered
Auth stub
POST /route
starting test
1) should post
after hook
1 failing
1) route
POST /route
should post:
Uncaught AssertionError: expected { Object (_events, _eventsCount, ...) } to have status code 200 but got 401
From this, we can see that the after the stub is registered, I can call it in the test file and it is properly stubbed. But when passport.authenticate() is called in route.post(), it is the actual passport.authenticate() and sends a response with status 401 because I'm not authenticated.
Any thoughts on what's going on?
Your code calls passport.authenticate as soon as it runs, and it runs as soon as it is required.
Because you are requiring the code at the beginning of the test before the stub has been created your code ends up calling the real passport.authenticate.
In order for code like this to call the stub you must set up your stub before you require your code:
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
const passport = require('passport');
// const server = require('../../app'); <= don't require your code here...
const expect = chai.expect;
chai.use(chaiHttp);
describe('route', function () {
before(function (done) {
sinon.stub(passport, 'authenticate').callsFake(function (test, args) {
console.log('Auth stub');
});
console.log('stub registered');
passport.authenticate('jwt', { session: false });
done();
});
after(function (done) {
console.log('after hook');
passport.authenticate.restore();
done();
});
describe('POST /route', function () {
it('should post', function (done) {
console.log('starting test');
const server = require('../../app'); // <= require it AFTER creating the stub
chai.request(server)
.post('/route')
.end(function (err, res) {
expect(res).to.have.status(200);
done();
});
});
});
});
One small note related to sinon stubbing, I manage to have it working using this syntax:
passport.authenticate = sinon.stub( passport, 'authenticate' )
.returns(
( req, res, next ) => {
const user = {
username: 'test user',
email: 'testuser#mail.com',
displayName: 'Test User',
provider: 'testCase',
roles: [ 'admin' ]
}
req.user = user;
next()
}
);
Main things are:
add user data into req.user
execute next() for code flow to continue into next middleware
To recap this is not a way to actually test your authentication strategy but to bypass it in case you want to test router results.
So, I learn to make a TDD. I have a simple API with NodeJS using Express Framework. This API only gives redirect function
router.route('/')
.get(function(req, res, next) {
res.status(200)
res.redirect('/Login')
})
.post(function(req, res, next) {
});
My Question is how to make unit test using mocha chai based on this API?
I'm trying to do something like this, but it passed even though I give 400 statuscode in the test
var app = require('../routes/index');
var should = require('chai').should(),
expect = require('chai').expect,
supertest = require('supertest'),
api = supertest('http://localhost:3030');
describe('/ ', function() {
it('Should redirect to /Login ', function() {
api.get('/')
.expect(400)
.end(function(err, res) {
expect(res.statusCOde).to.equal(400);
done();
})
})
})
Information on the express-jwt module can be found here:
https://github.com/auth0/express-jwt
https://www.npmjs.com/package/express-jwt
In my main.js server file, I have the following:
import ExpressJwt from 'express-jwt';
// import other crap...
let token = ExpressJwt({
secret: 'whatever',
audience: 'whatever',
issuer: 'whatever'
});
app.all('/apiv1', token.unless({ path: ['apiv1/user/create', '/apiv1/auth/login']}));
app.use('/apiv1/user', user);
app.use('/apiv1/auth', auth);
Where user and auth are the middlewares that handle my routes. What I want to do is obvious; deny API access to all unauthenticated users, except when they attempt to create a new user via apiv1/user/create and/or login via apiv1/auth/login.
Any time I try to make a request to the aforementioned unprotected paths however, I get the error:
UnauthorizedError: No authorization token was found
It's still protecting the routes I specified to be unprotected! I also tried:
app.use('/apiv1/user', token.unless({ path: ['/apiv1/user/create'] }), user);
app.use('/apiv1/auth', token.unless({ path: ['/apiv1/auth/login'] }), auth);
But that didn't work. I also tried using regex for the unless paths, but that didn't work either.
I arrived at app.all('/apiv1', token...) via this answer, but that solution does not yield me the desired functionality.
Instead of using all:
app.all('/apiv1', token.unless({ path: ['apiv1/user/create', '/apiv1/auth/login']}));
Try using use and adding in the path route a slash / at the beginning:
app.use('/apiv1', token.unless({ path: ['/apiv1/user/create', '/apiv1/auth/login']}));
Here it is an example that is working:
app.js:
var express = require('express');
var app = express();
var expressJwt = require('express-jwt');
var jwt = require('jsonwebtoken');
var secret = 'secret';
app.use('/api', expressJwt({secret: secret}).unless({path: ['/api/token']}));
app.get('/api/token', function(req, res) {
var token = jwt.sign({foo: 'bar'}, secret);
res.send({token: token});
});
app.get('/api/protected', function(req, res) {
res.send('hello from /api/protected route.');
});
app.use(function(err, req, res, next) {
res.status(err.status || 500).send(err);
});
app.listen(4040, function() {
console.log('server up and running at 4040 port');
});
module.exports = app;
test.js:
var request = require('supertest');
var app = require('./app.js');
describe('Test API', function() {
var token = '';
before(function(done) {
request(app)
.get('/api/token')
.end(function(err, response) {
if (err) { return done(err); }
var result = JSON.parse(response.text);
token = result.token;
done();
});
});
it('should not be able to consume /api/protected since no token was sent', function(done) {
request(app)
.get('/api/protected')
.expect(401, done);
});
it('should be able to consume /api/protected since token was sent', function(done) {
request(app)
.get('/api/protected')
.set('Authorization', 'Bearer ' + token)
.expect(200, done);
});
});
I have to test my rest api. Some routes require a value in the http requests headers for the user authentication token.
I have separated my interesting bussiness logic in pure javascript code but I can't find a way to test the routes that require a token in the headers of the http request.
Any other alternatives to mocha and/or supertest are welcome.
With supertest, you can set a header parameter with the set keyword :
api.get('/aroute/')
...
.set('headerParameterName', value)
...
Here is an example of testing a express server API with token authorization using supertest :
app.js:
var express = require('express');
var app = express();
var jwt = require('jsonwebtoken');
var expressJwt = require('express-jwt');
var secret = 'my-secret';
app.get('/get-token', function(req, res) {
var token = jwt.sign({foo: 'bar'}, secret);
res.send({token: token});
});
app.post(
'/test',
expressJwt({
secret: secret
}),
function(req, res) {
res.send({message: 'You could use the route!'});
}
);
app.use(function(err, req, res, next) {
res.status(err.status || 500).send({error: err.message});
});
app.listen(4040, function() {
console.log('server up and running at 4040 port');
});
module.exports = app;
test.js:
var request = require('supertest');
var app = require('./app.js');
describe('Test Route with Token', function() {
var token = '';
before(function(done) {
request(app)
.get('/get-token')
.end(function(err, res) {
var result = JSON.parse(res.text);
token = result.token;
done();
});
});
it('should not be able to consume the route /test since no token was sent', function(done) {
request(app)
.post('/test')
.expect(401, done);
});
it('should be able to consume the route /test since token valid was sent', function(done) {
request(app)
.post('/test')
.set('Authorization', 'Bearer ' + token)
.expect(200, done);
});
});