express-jwt Not respecting unprotected paths - node.js

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);
});
});

Related

html file does not appear after logging without any error

I am new to server-side programming.
I am trying to serve a HTML file (mapview.html) after authentication ,but it does not appear without any error.
there is no problem with authentication process. I expect when I click on login button, the codes check req data
and after validating, mapview.html pop up but nothing happen.
res.sendFile() causes in jquery part, console.log(res), get me all html codes in console line of chrome.
files directory:
src
index.js
db
public
index.html
mapview.html
middleware
auth.js
routers
user
task
model
user
task
index.html
$('div[name="logIn"]').click( () => { // select a div element that its name is logIn
console.log('LOG-IN:')
$.ajax({
url: '/login',
data: $('#loginForm').serialize(), // Get email and pass from login form
type: 'POST',
success: function (res) {
console.log(res)
}
})
})
user.js
router.post('/login', async (req, res) => {
try {
const user = await User.findByCredentials(req.body.email, req.body.password)
const token = await user.generateAuthToken()
const publicDir = path.join(__dirname, '../public')
res.sendFile(path.join(publicDir + '/mapview.html'));
} catch (e) {
res.status(400).send(e)
}
})
index.js
const express = require('express');
require('./db/mongoose');
const bodyParser = require('body-parser');
const userRouter = require('./routers/user');
const taskRouter = require('./routers/task');
const app = express();
const port = process.env.PORT || 3000;
app.use(express.json()); // Parse recieved json body data from Postman
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(express.static(__dirname + '/public'));
app.use(userRouter);
app.use(taskRouter);
app.listen(port, () => {
console.log('server is run on port:' + port)
});
when you are making the HTTP post request from index.html using ajax to verify user authentication details, on successful authentication you are sending a static html file which is simply a text response and will not be rendered as a web page (as you were expecting).
To solve this problem,
Create a separate route for accessing the mapview.html
app.get('/map', (req, res) => {
res.sendFile(__dirname + '/public' + '/mapview.html');
});
In your ajax response just redirect to the map route
$.ajax({
url: '/login',
data: $('#loginForm').serialize(), // Get email and pass from login form
type: 'POST',
success: function (res) {
console.log(res);
window.location.href = '/map'; // redirect to map route
}
});
I updated the codes and it looks like following code .
As I mention in the past comment , I need to authenticate before refirect through .href = '/map' and I do not know how to attach token to .href='/map.
we usually send token as header with ajax like this:
headers:{"Authorization": localStorage.getItem('token')}
in case I add it to .href ,something like this window.location.href = '/map + "?token=MY_TOKEN" , how can i get it in auth method?
user.js
router.post('/login', async (req, res) => {
try {
const user = await User.findByCredentials(req.body.email,
req.body.password)
const token = await user.generateAuthToken()
res.send({user, token})
} catch (e) {
res.send(e)
res.status(400).send(e)
}
})
router.get('/map', auth, (req, res) => {
const publicDir = path.join(__dirname, '../public')
res.sendFile(path.join(publicDir + '/mapview.html'));
});
index.html
$('div[name="logIn"]').click( () => {
$.ajax({
url: '/login',
data: $('#loginForm').serialize(),
type: 'POST',
success: function (res) {
localStorage.setItem('token', res.token);
window.location.href = '/map';
}
})
})

Digest auth using http-auth

Trying to implement Digest auth in nodejs. Below is the code
var http = require('http');
var auth = require('http-auth');
var express = require('express');
var app = express();
var user;
var basic = auth.basic({
realm: 'Sample',
file: __dirname + "/users.htpasswd",
algorithm:'md5'
});
basic.on('success', (result, req) => {
console.log(`User authenticated: ${result.user}`);
user = result.user;
});
basic.on('fail', (result, req) => {
console.log(`User authentication failed: ${result.user}`);
console.log(req.headers.authorization);
});
basic.on('error', (error, req) => {
console.log(`Authentication error: ${error.code + " - " + error.message}`);
});
http.createServer(app).listen(8000);
app.use(auth.connect(basic));
app.get('/', function(req, res) {
console.log(req.headers);
console.log(basic);
res.json('Hello from '+ user);
res.end();
});
app.post('/', function(req, res) {
console.log(req.headers);
console.log(basic);
res.json('Hello from '+ user);
res.end();
});
This is the content of users.htpasswd file:-
ankit:Sample:e4b2d19b03346a1c45ce86ad41b85c5e
Using postman to call the end point with username ankit, pwd ankit & realm Sample, everytime I am getting 401.
Please let me know where I am doing wrong.
Thanks
You're mixing basic auth and digest auth. Replace auth.basic with auth.digest and your code should work as-is.

Mocking req.user object for MochaJS tests

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?

Error: listen EADDRINUSE 3443

I have two app.listens in the code and I get an error ,when I take one of them out ,the authentication stops working ,how can I resolve the problem ,I'm trying to implement passport js into my app (I have followed a tutorial and I have passport js example working I just want to implement it to myproject )this is the following error I'm getting
screenshot here
'use strict'
const express = require('express')
const fs = require('fs')
const https =require('https')
const path = require('path')
var app = express();
var bodyParser = require('body-parser');
var morgan = require('morgan');
var mongoose = require('mongoose');
var jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens
var config = require('./config'); // get our config file
var Index = require('./api/planner/index'); // get our mongoose model
var port = process.env.PORT || 3443; // used to create, sign, and verify tokens
mongoose.connect(config.database); // connect to database
app.set('superSecret', config.secret); // secret variable
// use body parser so we can get info from POST and/or URL parameters
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// use morgan to log requests to the console
app.use(morgan('dev'));
// =======================
// routes ================
// =======================
// basic route
app.get('/', function(req, res) {
res.send('Hello! The API is at http://localhost:' + port + '/api');
});
// API ROUTES -------------------
// get an instance of the router for api routes
var apiRoutes = express.Router();
// TODO: route to authenticate a user (POST http://localhost:8080/api/authenticate)
apiRoutes.post('/authenticate', function(req, res) {
// find the user
User.findOne({
name: req.body.name
}, function(err, user) {
if (err) throw err;
if (!user) {
res.json({ success: false, message: 'Authentication failed. User not found.' });
} else if (user) {
// check if password matches
if (user.password != req.body.password) {
res.json({ success: false, message: 'Authentication failed. Wrong password.' });
} else {
// if user is found and password is right
// create a token
var token = jwt.sign(user, app.get('superSecret'), {
expiresIn: 1440 // expires in 24 hours
});
// return the information including token as JSON
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
}
});
});
// TODO: route middleware to verify a token
apiRoutes.use(function(req, res, next) {
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.token || req.headers['x-access-token'];
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
} else {
// if everything is good, save to request for use in other routes
req.decoded = decoded;
next();
}
});
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
});
// route to show a random message (GET http://localhost:8080/api/)
apiRoutes.get('/', function(req, res) {
res.json({ message: 'Welcome to the coolest API on earth!' });
});
// route to return all users (GET http://localhost:8080/api/users)
apiRoutes.get('/users', function(req, res) {
User.find({}, function(err, users) {
res.json(users);
});
});
// apply the routes to our application with the prefix /api
app.use('/api', apiRoutes);
// we'll get to these in a second
app.get('/setup', function(req, res) {
// create a sample user
var nick = new User({
name: 'Nick Cerminara',
password: 'password',
admin: true
});
// save the sample user
nick.save(function(err) {
if (err) throw err;
console.log('User saved successfully');
res.json({ success: true });
});
});
// =======================
// start the server ======
// =======================
app.listen(port);
console.log('Magic happens at http://localhost:' + port);
const directoryToServe = 'client'
//const port = 3443
app.use('/',express.static(path.join(__dirname,'..',directoryToServe)))
const httpsOptions = {
cert: fs.readFileSync(path.join(__dirname,'ssl','server.crt')),
key: fs.readFileSync(path.join(__dirname,'ssl','server.key'))
}
https.createServer(httpsOptions, app)
.listen(port, function()
{
console.log(`Serving the ${directoryToServe}/directory at https://localhost:${port}`)})
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app
app.get('/', function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("We're up and running!!!");
});
var plan = require('./api/planner/index');
app.get('/api/planner',plan.index);
app.post('/api/planner',plan.create);
app.put('/api/planner/:id',plan.update);
app.delete('/api/planner/:id',plan.delete);
console.log("Server running at http://127.0.0.1:8000/");
//
This err is probably caused by using a busy port. If you are using ubuntu, you can check the status of ports by lsof -i:portnumber. After running the command you will have a PID.
You can release the port by kill -9 pid.
Windows and mac have similar commands.

How to test an express rest api with header parameters using mocha and supertest?

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);
});
});

Resources