Returning 401 error for all routes with hapijs - node.js

We're using Hapi JS for our rest server. We store the authentication tokens for the users on Redis. Now, if for some reason node loses connection with Redis, we need to return 401 Authorization failed error to all the clients from all the routes so the clients can logout automatically.
So, is there a way to return 401 from all routes without changing the code in the route handler functions?

You can make use of the Hapi server extension event 'onRequest'.
var hapi = require('hapi');
var Boom = require('boom');
var server = new hapi.Server();
//Configure your server
//Add an extension point
server.ext('onRequest', function (request, reply) {
var status;
//Check status of redis instance
if (status) {
//Redis is running, continue to handler
return reply.continue();
} else {
//Redis is down, reply with error
return reply(Boom.unauthorized('Auth server is down'));
}
});
This is probably not how you will verify the status your redis instance, but I hope you get the point.
One can look up various other extension points here.

You should do this in the auth plugin used by your app. Take a look at the hapi-auth-basic implementation: https://github.com/hapijs/hapi-auth-basic/blob/master/lib/index.js
If you look in the scheme you must define an authenticate method which takes the request and reply. This is where you should check redis for an auth token. If the connection is not available you should
return reply(Boom.unauthorized('Authorization failed', 'Basic'));
Hope this helps.

Related

Websockets token authentication using middleware and express in node.js

I use node.js, express and express-ws that is based on ws
Express-ws allows to create express-like endpoints for websockets.
I am looking for a solution to authenticate users in websocket connections, based on a token. Since my ws server is based on an HTTP one
const wsHttpServer = http.createServer();
wsHttpServer.listen(5001);
const expressWs = require('express-ws')(app , wsHttpServer);
and since the ws connection is based on an HTTP one that gets upgraded to a ws, WHY I cannot pass a token in my ws that the express route checks, like any other one? My logic is, send the token, check it, if it is ok, proceed to upgrade to a ws connection. So, I can reuse the token-middleware solution that I have in my HTTP connections.
In node
My ws server
const wsHttpServer = http.createServer();
wsHttpServer.listen(5001);
const expressWs = require('express-ws')(app , wsHttpServer);
//set the route
app.use('/ws', require('./routes/wsroute'));
In that route, I would like to use the token.validate() middleware -that in HTTP connections, checks the Authorization header
router.ws('/user/:name/:id', token.validate(), (ws, req) => {
console.log('ws route data : ',vessel, req.params.name, req.params.id);
});
In my client
const socket = new WebSocket('ws://localhost',{
path: '/user/Nick/25/',
port: 5001, // default is 80
protocol : "echo-protocol", // websocket protocol name (default is none)
protocolVersion: 13, // websocket protocol version, default is 13
keepAlive: 60,
headers:{ some:'header', 'ultimate-question':42 } // websocket headers to be used e.g. for auth (default is none)
});
this errors Failed to construct 'WebSocket': The subprotocol '[object Object]' is invalid
I also tried
const socket = new WebSocket('ws://localhost:5001/user/Nick/25', ["Authorization", localStorage.getItem('quad_token')]);
I dont get any errors, but I dont know how to get the Authorization "header" in node
I could
just send const socket = new WebSocket(currentUrl); with some data and include a valid token in that data. But to check it, I have to allow the connection first. I dont want that, I would like to use a middleware solution that automatically checks a token and allows or not to continue.
Questions
Please help me understand:
1 Is it possible to use a token-based, middleware-based solution in ws?
2 How to set a header with a token in a ws connection?
3 How to get that token in node?
1) In my experience there is no available express.js middleware and the solution i found requires to listen to the upgrade event on your http server and blocking access to your socket connection before it reaches ws routes.
2) Your browser will not allow setting additional headers during websocket connection on the client side. It will send though the cookies so you can make use of express-session to authorize on your server first the user, a cookie will be set on the browser and that cookie will be sent over during the websocket connection.
3) You can do like in this answer Intercept (and potentially deny) web socket upgrade request Copying the code here from there for your own perusal.
**wsHttpServer**.on('upgrade', function (req, socket, head) {
var validationResult = validateCookie(req.headers.cookie);
if (validationResult) {
//...
} else {
socket.write('HTTP/1.1 401 Web Socket Protocol Handshake\r\n' +
'Upgrade: WebSocket\r\n' +
'Connection: Upgrade\r\n' +
'\r\n');
socket.close();
socket.destroy();
return;
}
//...
});
As outlined here, it seems that it is not possible for a standard browser websocket client to handle a http error response to an upgrade request. Thus what I ended up using was something like this:
HTTPserver.on('upgrade' (req, sock, head) => {
if (req.url === wsRoute) {
webSocketServer.handleUpgrade(req, sock, head, ws => {
const authenticated = validateToken(req.headers.cookie) // your authentication method
if (!authenticated) {
ws.close(1008, 'Unauthorized') // 1008: policy violation
return
}
webSocketServer.emit('connection', ws, req)
})
} else {
sock.destroy()
}
}
This way we accept the connection first before closing it with an appropriate code and reason, and the websocket client is able to process this close event as required.
On your client side, you should pass an array of strings instead of object, but you must set a header for your HTTP response with a key and value:
key : headeSec-WebSocket-Protocol
value : corresponding protocol used in front.

Is it okay to not send a response to urls that people are pentesting my node/express site?

I log all 404s on my website. I keep getting them for pages I haven't linked to, and it's clearly someone (a bot) trying to find admin pages / secure files on my site such as /wp-admin.php;
router.get('/wp-admin.php', function(req, res, next) {});
I tried this and it doesn't seem to hold up the server, it just outputs something like this a minute later:
GET /wp-admin.php - - ms - -
Is there any detriment to adding routes such as that, where no response is sent, possibly wasting their time?
router.get('/wp-admin.php', function(req, res, next) {});
This will cause express to time out and close the connection. This will make Denial of Service attack easier for hackers and jam up your node server.
You can always use some kind of rate limiters to prevent continuous request from a certain IP.
express-rate-limit
is a can be used for this. It is simple express middleware
As noted in the already accepted answer, an Express route like that will leave you vulnerable.
I recommend going one step further and tearing down those requests using req.destroy.
I'm not sure of the implications of Express being included, here, though. For example, is the request body being read automatically by a middleware upstream of this request handler you've shown? If so, that would be an attack vector that makes the mitigation I'm suggesting useless.
Regardless, to demonstrate what I am suggesting with a vanilla HTTP server:
var h = require('http')
h.createServer(function(req, res) {
// tear down the socket as soon as the request event is emitted
req.destroy()
}).listen(8888, function() {
// send a request to the server we just created
var r = h.request({port: 8888})
r.on('response', console.log.bind(console, 'on_response'))
r.on('error', console.log.bind(console, 'on_error'))
r.on('timeout', console.log.bind(console, 'on_timeout'))
// abort will be emitted to the caller, but nothing else
r.on('abort', console.log.bind(console, 'on_abort'))
r.end()
})
You could also call socket.destroy in the connection event of the HTTP server if you're able to identify the calling agent as a bot (or whatever) somehow.
var h = require('http')
h.createServer(function(req, res) {
res.send('foo')
}).on('connection', function(socket) {
// pretend this ip address is the remote address of an attacker, for example
if (socket.remoteAddress === '10.0.0.0') {
socket.destroy()
}
}).listen(8888, function() {
// send a request to the server we just created
var r = h.request({port: 8888})
r.on('response', console.log.bind(console, 'on_response'))
r.on('error', console.log.bind(console, 'on_error'))
r.on('timeout', console.log.bind(console, 'on_timeout'))
// abort will be emitted to the caller, but nothing else
r.on('abort', console.log.bind(console, 'on_abort'))
r.end()
})

NodeJS express framework reads the same event multiple times

I'm working with a service (WSO2CEP) that sends events to a node js program that I developed, let's call it receiver.js, and then it stores these events in a mongo db. The comunication between WSO2CEP and receiver.js is done through a HTTP connection. The problem I'm facing on is that when the WSO2 sends an event, the receiver.js caputres it and stores it in the db, and after a few seconds/minutes, it detects that a new events has arrived, which is not true, and stores it again in the db. This second event is identical to the first one.
When I saw that I thought that the problem was that the WSO2 was sending the same event multiple times, but I've debug it an I'm 100% sure that only one events is being sent, so the problem seems to be the HTTP connection.
The HTTP connection is being handled by the receiver.js acting as a server and WSO2 as a client, which sends the events through HTTP post request. The http server implementation in receiver.js is done with the "express" framework. As it can be seen in below code chunk.
'use strict';
const express = require('express');
const bodyParser = require('body-parser');
const EventEmitter = require('events');
const port = Whatever;
module.exports = class WSO2Server extends EventEmitter {
constructor () {
super();
const app = express();
app.use(bodyParser.json()); // to support JSON-encoded bodies
app.route('/Whatever').post( (req, res) => {
let event = req.body;
this.emit('event', event);
});
this.server = app.listen(port);
}
destroy () {
this.server.close();
}
}
I suspect that the events are being stored in a queu (or similar) and are being retransmitted every so often. Any idea about that? Thank you
Looking at your code, I can't see you using the response object at all. After you've called this.emit('event', event); you should call something like res.status(201).end(); which will dispatch a HTTP Status 201 back to the calling client.
Because you're not setting any information on the response object, your application is hanging and not making a response to the HTTP call. Thus something like nginx or apache is re-issuing the request to your application after a specific timeout.
If you explicitly create the response with something res.status(201).end(); then your request will end correctly and a duplicate call will not be made.

How can I get (Express's) sessionID for a SockJS connection

I am using SockJS on Express server. Is there any way to get the associate HTTP session ID of the client?
I see there is a way to do it for raw web socket and Socket.io, but I am struggling to find how to do it for SockJS.
This is how my server looks like. I want a similar handler to fetch session ID:
var sockjs_echo = sockjs.createServer(sockjs_opts);
sockjs_echo.on('connection', function(conn) {
conn.on('data', function(message) {
conn.write(message);
});
});
This is a "hack", but it works for me:
sockjs_echo.on('connection', function(conn) {
var cookieHeader = conn._session.recv.ws._stream._readableState.pipes._driver._request.headers.cookie
var cookies = {}
cookieHeader.split(';').forEach(function( cookie ) {
var parts = cookie.split('=');
cookies[parts.shift().trim()] = decodeURI(parts.join('='));
});
conn.on('data', function(message) {
conn.write(message);
});
});
'cookies' variable (example):
{
"dev_cookie": "1mimec6rbcolp0ujkcbqq9pdq4uoa5v0p8a284v32tmd4q3k0qi9p4vjteoifdn9b0lsm238fghf974o9jfehfuhvm3ltrgq02ad6k0",
"session_cookie": "s%3AjkKYPKFFT8r60rXUsVYISoOF17o49GUl.pbpu6T1%2BcdrIu5uQPRxZUYOrl5GnC179GaI5pWyR7SA",
"other_cookie": "s%3AzRMiC3fjo4gxTXX1p2XSi_C_EydIa358.KAdP1gwtZBVfcbkmwi%2B3pa0L1pbOCzQ3lHnNEyFvvHc"
}
Thanks so much for asking this question, #darwinbaisa, and for the answer, c-toesca. This came after days of searching.
For XHR streaming, the cookies are at: conn._session.recv.request.headers.cookie.
The only other way I could think of doing this was to make the express session cookie httpOnly: false, thus exposing it to javascript and, of course, the possibility of hacking, then pass it back as a prefix to any message from the SockJS javascript client to the node server.
Or to assign the ID to a javascript variable as I dynamically wrote a web page response, so that javascript would have access to the variable, and again could return it to the server. But again, this would have exposed the ID, and even if the ID was hashed or encrypted, it could still be used in a malicious call to the server from javascript.
Things like this are made a lot easier in the node WS library, but I need a fallback from that for websocket-challenged browsers.

Get SESSIONID in nodeJS

Now, after some hours of playing around with nodejs and socket.io, I'm getting a couple more problems - one being, that I need to get the sessionID inside node.js, whitout using app.get('/' ... - since that event doesnt seem to fire when socket.io connects, it only fires .on('connection', function( ...
var express = require('express')()
express.set('port', process.env.PORT || 8080)
var server = require('http').createServer(express)
var socket = require('socket.io').listen(server)
server.listen(express.get('port'))
// this event is fired, get('/', ... isnt!
server.on('connection', function(stream) {
// ??
} )
The Session is initially created by the PHP application, as the user logs in. Session data is stored in the database, and the key I need to access that data is the SESSION ID. What's the easiest way to get to it? Like mentioned, I found a couple examples that used app.get('/' ... but I couldnt get that event to fire at all.
Thanks.
If the session data is being stored as a cookie (most likely), then you should be able to re-parse that data during the socket handshake. I posted code for that on this answer, but copied the code here as well:
io.configure(function () {
io.set('authorization', function (handshakeData, callback) {
var cookie = handshakeData.headers.cookie;
// parse the cookie to get user data...
// second argument to the callback decides whether to authorize the client
callback(null, true);
});
});
If the session data is being propagated in the URL, then you may be able to gather this information from handshakeData.url or handshakeData.query. You'll probably have to do your own experimentation.

Resources