I'd like to implement http basic auth authentication into my nodejs websocket app. Here's my piece of code:
var http = require('http');
var auth = require('http-auth');
var express = require('express');
var app = express();
//....
basic = auth.basic({
realm: "websocket auth",
file: __dirname + "/users.htpasswd"
});
var server = http.createServer(basic);
app.listen = function() {
return server.listen.apply(server, arguments);
};
// WEBSOCKET
var WebSocketServer = require('websocket').server;
var wss = new WebSocketServer({
httpServer: server
});
wss.on('request', function(request) {
var connection = request.accept();
connection.sendUTF('Server: message ws auth');
});
For standard http access authentication it works fine, but I can't get it working on websocket, when I connect through websocket I immediately get response. Is it even possible to authenticate via http on websocket?
Not directly.
Websockets aren't HTTP so they don't do things like HTTP AUTH, send cookies, etc.
For our websocket connections we pass in the cookie as a parameter on the request URL. We are using socket.io, however, which has a provision for setting an authentication function sort of similar to an HTTP server's middleware.
You should be able to do something similar with the #accept method from Websocket-node. Examine the data coming in and if it's not authorized, reject the connection.
Related
I am running a websocket (wss) server (with certificate) on port 443. For this i am using the "https" and "ws" module in nodejs (simplified):
require(__dirname + './config.js');
const https = require('https');
const WebSocketServer = require('ws');
const server = new https.createServer({
key: fs.readFileSync('./privkey.pem'),
cert: fs.readFileSync('./fullchain.pem')
});
const wss = new WebSocketServer.Server({server: server});
wss.on("connection", function(socket){
const c_inst = new require('./client.js');
const thisClient = new c_inst();
thisClient.socket = socket;
thisClient.initiate();
socket.on('error', thisClient.error);
socket.on('close', thisClient.end);
socket.on('message', thisClient.data);
});
server.listen(config.port, config.ip);
The wss communication works perfect! But if i open the wss url via https:// in the browser i get a timeout error (Error code 524) because there is no response for a "normal" (https/get) request.
How can i implement a response for this case in my wss server?
I just want to send a response with a simple text or html like "This is a websocket server" or do a redirect to another url if someone connects to the wss socket via browser/https.
Thanks!
See the documentation for HTTPS server on node.
https://nodejs.org/api/https.html#httpscreateserveroptions-requestlistener
You need to add request listener when you create a server.
const server = new https.createServer(
{
key: fs.readFileSync('./privkey.pem'),
cert: fs.readFileSync('./fullchain.pem')
},
(req, res) => {
res.writeHead(200);
res.end('This is a websocket server\n');
// do redirects or whanever you want
}
);
I am using socketio-jwt to authenticate sockets.
On the server:
socketio.use(require('socketio-jwt').authorize({
secret: config.secrets.session,
handshake: true
}));
On the client:
var ioSocket = io('', {
query: 'token=' + Auth.getToken(),
path: '/socket.io-client'
});
My problem is, that if a client is connecting to the server, the authentication fails, and no connection to with the socket is established. Now if the user logs into the system, the connection with the socket is remains not established.
What I try to achieve is, that if a user logs into the system, the connection gets established.
My only idea so far is to reload the page with something like $window.location.href = '/'; after login. But it seems like not the proper way.
Another option would be to keep the socket trying to (re)connect with a timeout. But its a bad option, since my application allows users without login.
How to properly trigger a socket connection?
Here is the solution to your problem
Firstly on your server side you can implement and bind the user credentials to a token as shown below.
var jwt = require('jsonwebtoken');
app.post('/loginpage',function(req,res){
//validate the user here
});
//generate the token of the user based upon the credentials
var server = //create the http server here.
In the socket.io server side you can do the following
//require socket.io
var xxx = socketIo.listen //to the server
xxx.set('auth',socketIojwtauthorize({
secret: config.secrets.session,
handshake: true
}));
xxx.sockets //here connection on or off by setting up promise
//server listen to localhost and port of your choice
When the client sends a valid jwt connection is triggered
Simple client side file would contain the following modules
//function connectsocket(token){
//connect(
//and query the token );
}
//socket.on('connect',function(){
//generate the success message here
}).on('disconnect',function(){ //your choice })
//rendering the routes with the server side
$.post('/loginroute',{
//user credentials passes here
}).done(function(bind){ connect_socket( bind.token )})
var socket = require('socket.io');
var express = require('express');
var http = require('http');
var app = express();
var server = http.createServer(app);
var io = socket.listen(server);
io.on('connection', function (client) {
// here server side code
})
server.listen(5000);
on client side include
node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js
socket = io.connect(site_url + ":5000", { reconnect: !0 });
I am pretty new to nodejs and very new to socket.io and express. I have some code that is working, but am having trouble understanding exactly why it's working.
My question for the below code is how does the express app know to listen on port 80? There's a server.listen. But there is no app.listen in the code. Yet app.post() readily accepts posted data.
Please consider the following code
var https = require('https');
var url = require('url');
var fs = require('fs');
var bodyParser = require('body-parser');
var express = require('express');
var app = express();
//var io = require('socket.io');
var zlib = require('zlib');
app.use(bodyParser.urlencoded({extended: true }));
var options = {
key: fs.readFileSync('my.key'),
cert: fs.readFileSync('my.crt')
};
var serverPort = 80;
var server = https.createServer(options, app);
var io = require('socket.io')(server);
// log when the server is ready
server.listen(serverPort, function() {
console.log('Web socket server up and running at port %s', serverPort);
// this prints 80, as expected
});
app.post('/api', function (req, res) {
// working code is in here that receives the post variables and responds
}
With Express, the app object is just a request handler for some http server. If you call app.listen(), then the app object will create an http server for you. But, if you have created your own http server (which your code example does), then the app object just becomes a request listener on that server with this line of your code:
var server = https.createServer(options, app);
That creates the http server and registers app as a request listener (so it sees all incoming requests). This allows the Express app object to then process the routes that are registered with it to server routes like app.get(...) or app.post(...).
And, the port for Express is the port for the http server so it's the port that was used when the http server was created. What's important to understand here is that Express is not its own server. It's just a request listener for some http server.
In your code example, your http server is set up for port 80 so that's the port being used and Express gets registered as a request handler on all those incoming http requests on that port.
If you look at the source code for app.listen(), you see this:
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
All, it does is create an http server with the app object as a listener and then call .listen() on that new server.
the app does not have to know on which port to listen. Basically, server is your HTTP server binding which listens on port 80 in your example. var server = https.createServer(options, app); then tells the server to listen on port 80 for HTTP requests and forward them to your app. The app then does the routing stuff that links the function you sepcified with app.post(...) to a specific request URL (/api in this case).
TL;DR: The app does not need to listen, because the server is the only communication interface to the outside.
I am trying to make socket.io work both on http and https connections, but it seems that with my current configuration it can work only on one of them.
With the below config options it can access my application through https, but when trying to access it through http it cannot connect and I receive errors:
var app = express()
, http= require('http').createServer(app)
, https = require('https').createServer(options, app)
, io = require('socket.io').listen(https, { log: false })
And later I have this:
http.listen(80, serverAddress);
https.listen(443, serverAddress);
On client side I have this:
<script src='/socket.io/socket.io.js'></script>
var socket = io.connect('https://<%= serverAddress %>', {secure: true, 'sync disconnect on unload' : true});
Of course if I switch the http with the https options on the .listen and .connect functions of the server and the client respectively I am having the reverse results, e.g. it can access through http and not through https.
How is it possible to achieve this? I need it mostly because it is regarding a Facebook app, so it must provide both http and https connection options according to Facebook's rules.
Edit: In case it helps about the problem, the error I am receiving is this:
Failed to load resource: the server responded with a status of 404 (Not Found) http://DOMAIN/socket.io/socket.io.js
And because of this I get others such as:
Uncaught ReferenceError: io is not defined
I believe the problem is in your way of setting up socket.io on the server side and on the client.
Here's how I made it work (just for you).
Server:
var debug = require('debug')('httpssetuid');
var app = require('../app');
var http = require('http');
var https = require('https');
var fs = require('fs');
var exec = require('child_process').exec;
var EventEmitter = require('events').EventEmitter;
var ioServer = require('socket.io');
var startupItems = [];
startupItems.httpServerReady = false;
startupItems.httpsServerReady = false;
var ee = new EventEmitter();
ee.on('ready', function(arg) {
startupItems[arg] = true;
if (startupItems.httpServerReady && startupItems.httpsServerReady) {
var id = exec('id -u ' + process.env.SUDO_UID, function(error, stdout, stderr) {
if(error || stderr) throw new Error(error || stderr);
var uid = parseInt(stdout);
process.setuid(uid);
console.log('de-escalated privileges. now running as %d', uid);
setInterval(function cb(){
var rnd = Math.random();
console.log('emitting update: %d', rnd);
io.emit('update', rnd);
}, 5000);
});
};
});
app.set('http_port', process.env.PORT || 80);
app.set('https_port', process.env.HTTPS_PORT || 443);
var httpServer = http.createServer(app);
var opts = {
pfx: fs.readFileSync('httpssetuid.pfx')
};
var httpsServer = https.createServer(opts, app);
var io = new ioServer();
httpServer.listen(app.get('http_port'), function(){
console.log('httpServer listening on port %d', app.get('http_port'));
ee.emit('ready', 'httpServerReady');
});
httpsServer.listen(app.get('https_port'), function(){
console.log('httpsServer listening on port %d', app.get('https_port'));
ee.emit('ready', 'httpsServerReady');
});
io.attach(httpServer);
io.attach(httpsServer);
io.on('connection', function(socket){
console.log('socket connected: %s', socket.id);
});
Client:
script(src='/socket.io/socket.io.js')
script.
var socket = io();
socket.on('update', function(update){
document.getElementById('update').innerHTML = update;
});
Here are the key points for the server:
require socket.io but don't call it's listen method yet (assuming http and https are already required). Instead, just keep the reference. (var ioServer = require('socket.io'))
create your http & https server
create a new instance of ioServer
bind your http and https servers (.listen)
attach http&https server instances to the io instance. (.listen is an alias for .attach)
setup io events.
And the client (jade syntax but you get the idea):
include socket.io script tag
call io and capture reference
setup your event handlers
On the client you don't need to call io.connect(). Furthermore, I'm not sure about your options there. It looks like you have a typo (, ,) and I can't find any reference to secure: true in the 1.0 documentation.
Arguably, the node.js server object for HTTP and HTTPS ought to be given the capability to listen on an arbitrary number of ports and interfaces, with and without SSL, but this does not seem to currently be implemented. (I was able to get one server to listen on two ports by passing a second server that had no request listener as the "handle" argument to server.listen(handle, [callback]) interface, in addition to server.listen(port, [hostname], [backlog], [callback]), but it did not work with SSL/non-SSL servers mixed.)
The stunnel workaround already mentioned is of course a viable option, but if it is not desirable to install a separate piece of software (to avoid non-node.js dependencies), the same tunneling can be achieved natively in node.js instead (assuming HTTP on port 80 and HTTPS on port 443):
var fs = require('fs');
var net = require('net');
var tls = require('tls');
var sslOptions = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem')
};
tls.createServer(sslOptions, function (cleartextStream) {
var cleartextRequest = net.connect({
port: 80,
host: '127.0.0.1'
}, function () {
cleartextStream.pipe(cleartextRequest);
cleartextRequest.pipe(cleartextStream);
});
}).listen(443);
This will have the same effect as using stunnel. In other words, it will avoid the need for two separate socket.io server instances, while also making the node.js "https" module redundant.
I have done something similar and it required two socket.io instances. Something like this:
var express = require('express');
var oneServer = express.createServer();
var anotherServer = express.createServer();
var io = require('socket.io');
var oneIo = io.listen(oneServer);
var anotherIo = io.listen(anotherServer);
Of course that you will need to inject messages twice: for both socket.io instances.
A good option is delegate SSL handling to stunnel and forget about SSL in your code.
I solved the problems using a different approach, I configured the server to support only unencrypted transport, and used stunnel for the https support.
For information on how to install stunnel you can check this post.
Then, used the following con configuration:
#/etc/stunnel/stunnel.conf
cert = /var/www/node/ssl/encryption.pem
[node]
accept = 443
connect = 80
Finally, I used the following to connect the clients:
var socket = that.socket = io.connect('//'+server);
This will auto detect the browser scheme and connect using http/https accordingly.
I am guessing Cross origin requests could be the reason why you are getting errors. Change in protocol is considered change in domain. So for page served via http server accessing https server (websocket server attached to it) may throw security errors. See an example here on how to enable CORS in express.
Also you should change * in the header to http://serverAddress , https://serverAddress. Allowing all sites is not a good idea, use it for testing.
The same is true if you are trying to AJAX between your http and https servers. Please post the errors, just to be sure about it.
I'm currently using nodejs with connect as my HTTP server. Is there anyway to activate HTTPS with connect? I cannot find any documentation about it. Thanks.
Herry
Instead of creating http server, use https server for connect :
var fs = require('fs');
var connect = require('connect')
//, http = require('http'); Use https server instead
, https = require('https');
var options = {
key: fs.readFileSync('ssl/server.key'),
cert: fs.readFileSync('ssl/server.crt'),
ca: fs.readFileSync('ssl/ca.crt')
};
var app = connect();
https.createServer(options,app).listen(3000);
See the documentation for https here and tls server (https is a subclass of tls) here
From http://tjholowaychuk.com/post/18418627138/connect-2-0
HTTP and HTTPS
Previously connect.Server inherited from Node’s core net.Server, this
made it difficult to provide both HTTP and HTTPS for your application.
The result of connect() (formerly connect.createServer()) is now
simply a JavaScript Function. This means that you may omit the call to
app.listen(), and simply pass app to a Node net.Server as shown here:
var connect = require('connect')
, http = require('http')
, https = require('https');
var app = connect()
.use(connect.logger('dev'))
.use(connect.static('public'))
.use(function(req, res){
res.end('hello world\n');
})
http.createServer(app).listen(80);
https.createServer(tlsOptions, app).listen(443);
The same is true for express 3.0 since it inherits connect 2.0