node.js, socket.io with SSL
Asked Answered
A

13

190

I'm trying to get socket.io running with my SSL certificate however, it will not connect.

I based my code off the chat example:

var https = require('https');
var fs = require('fs');
/**
 * Bootstrap app.
 */
var sys = require('sys')
require.paths.unshift(__dirname + '/../../lib/');

/**
* Module dependencies.
*/

var express = require('express')
  , stylus = require('stylus')
  , nib = require('nib')
  , sio = require('socket.io');

/**
 * App.
 */
var privateKey = fs.readFileSync('../key').toString();
var certificate = fs.readFileSync('../crt').toString();
var ca = fs.readFileSync('../intermediate.crt').toString();

var app = express.createServer({key:privateKey,cert:certificate,ca:ca });

/**
 * App configuration.
 */

...

/**
 * App routes.
 */

app.get('/', function (req, res) {
  res.render('index', { layout: false });
});

/**
 * App listen.
 */

app.listen(443, function () {
  var addr = app.address();
  console.log('   app listening on http://' + addr.address + ':' + addr.port);
});

/**
 * Socket.IO server (single process only)
 */

var io = sio.listen(app,{key:privateKey,cert:certificate,ca:ca});
...

If I remove the SSL code it runs fine, however with it I get a request to http://domain.example/socket.io/1/?t=1309967919512

Note it's not trying HTTPS, which causes it to fail.

I'm testing on chrome, since it is the target browser for this application.

I apologize if this is a simple question, I'm a node/socket.io newbie.

Amby answered 6/7, 2011 at 16:0 Comment(7)
Is your client trying to connect to a 'wss://' prefixed URI.Gymnasiast
nope it doesnt get there, it makes the request to domain.com/socket.io/1/?t=1309967919512 then dies.Amby
How are you specifying the address to connect to? "domain.com" sounds like a placeholder in the socket.io client-side library. Can you post your client Javascript code that you are using to connect?Gymnasiast
the project is on github: github.com/BCCasino/BCCasinoAmby
basically becasue its node.js socket.io magically handles the client side stuff, all you do is run socket.connectAmby
in github the code in question is test2.js and index.jade (backend and frontend respetively)Amby
I was trying to make a connection using wss uri it gives me URISyntaxException. Any idea why?Confidence
G
201

Use a secure URL for your initial connection, i.e. instead of "http://" use "https://". If the WebSocket transport is chosen, then Socket.IO should automatically use "wss://" (SSL) for the WebSocket connection too.

Update:

You can also try creating the connection using the 'secure' option:

var socket = io.connect('https://localhost', {secure: true});
Gymnasiast answered 6/7, 2011 at 18:17 Comment(4)
we do this. we goto https : // www.thebitcoinwheel.com and it still makes a request to http automatically, this is something with the socket.io code and is the point of the question.Amby
{secure: true} should not be needed if you specify 'https' in the url. Here is an excerpt from socket.io client source secure: 'https' == uri.protocol (version 0.9.16), it sets secure option to true if https is detected in the url.Baronial
I tried this with an https URL and indeed {secure: true} was not required to function correctly.Tiptop
I believe it would be prudent to assure that the connection is secure by using both secure:true and issuing an https url to the client side. This way no matter what you know it will be a secure connection.Tameika
S
75

The following is how I set up to set it up with express:

    var app = require('express')();
    var https = require('https');
    var fs = require( 'fs' );
    var io = require('socket.io')(server);

    var options = {
        key: fs.readFileSync('./test_key.key'),
        cert: fs.readFileSync('./test_cert.crt'),
        ca: fs.readFileSync('./test_ca.crt'),

        requestCert: false,
        rejectUnauthorized: false
    }

    var server = https.createServer(options, app);
    server.listen(8080);
    

    
    io.sockets.on('connection', function (socket) {
        // code goes here...
    });
    
    app.get("/", function(request, response){
        // code goes here...
    })
   

Update : for those using lets encrypt use this

var server = https.createServer({ 
                key: fs.readFileSync('privkey.pem'),
                cert: fs.readFileSync('fullchain.pem') 
             }, app);
Suds answered 22/7, 2016 at 11:36 Comment(8)
This is the only solution that worked for me. Thanks for saving my time.Dateless
worked good for me after a bit of trial and error with the certsShowman
This solution worked perfect for me, thanks. If you're using the free certs from letsencrypt.org then you can use the following code.. var server = https.createServer({ key: fs.readFileSync('/etc/letsencrypt/live/domain.name/privkey.pem'), cert: fs.readFileSync('/etc/letsencrypt/live/domain.name/cert.pem'), ca: fs.readFileSync('/etc/letsencrypt/live/domain.name/chain.pem'), requestCert: false, rejectUnauthorized: false },app); server.listen(3000);Benjaminbenji
Thanks a lot for this answer. It has been of tremendous help to me.Simon
I cracked my head looking for a solution in SocketIO and the issue was to config express, fantastic...Imogen
"rejectUnauthorized: false WARNING: it leaves you vulnerable to MITM attacks!"Optic
Thanks, worked like a charm with letsencrypt and .pem filesMcatee
This almost worked for me. I had to move the line var io = require('socket.io')(server); AFTER the one in which the server was declared though.Frohne
O
36

On the same note, if your server supports both http and https you can connect using:

var socket = io.connect('//localhost');

to auto detect the browser scheme and connect using http/https accordingly. when in https, the transport will be secured by default, as connecting using

var socket = io.connect('https://localhost');

will use secure web sockets - wss:// (the {secure: true} is redundant).

for more information on how to serve both http and https easily using the same node server check out this answer.

Ortensia answered 9/3, 2014 at 18:36 Comment(0)
T
14

If your server certificated file is not trusted, (for example, you may generate the keystore by yourself with keytool command in java), you should add the extra option rejectUnauthorized

var socket = io.connect('https://localhost', {rejectUnauthorized: false});
Trumpery answered 23/8, 2016 at 6:4 Comment(2)
would be appreciated if you added an example explaining how you used the keytool to create that key for node. 'Cause keys are so complicated and there just aren't enough tutorials about it.Welltimed
keytool is a tool inside the Java Development Kit (JDK). you can referer this docs.oracle.com/javase/10/tools/…Trumpery
S
6

Depending on your needs, you could allow both secure and unsecure connections and still only use one Socket.io instance.

You simply have to instanciate two servers, one for HTTP and one for HTTPS, then attach those servers to the Socket.io instance.

Server side :

// needed to read certificates from disk
const fs          = require( "fs"    );

// Servers with and without SSL
const http        = require( "http"  )
const https       = require( "https" );
const httpPort    = 3333;
const httpsPort   = 3334;
const httpServer  = http.createServer();
const httpsServer = https.createServer({
    "key" : fs.readFileSync( "yourcert.key" ),
    "cert": fs.readFileSync( "yourcert.crt" ),
    "ca"  : fs.readFileSync( "yourca.crt"   )
});
httpServer.listen( httpPort, function() {
    console.log(  `Listening HTTP on ${httpPort}` );
});
httpsServer.listen( httpsPort, function() {
    console.log(  `Listening HTTPS on ${httpsPort}` );
});

// Socket.io
const ioServer = require( "socket.io" );
const io       = new ioServer();
io.attach( httpServer  );
io.attach( httpsServer );

io.on( "connection", function( socket ) {

    console.log( "user connected" );
    // ... your code

});

Client side :

var url    = "//example.com:" + ( window.location.protocol == "https:" ? "3334" : "3333" );
var socket = io( url, {
    // set to false only if you use self-signed certificate !
    "rejectUnauthorized": true
});
socket.on( "connect", function( e ) {
    console.log( "connect", e );
});

If your NodeJS server is different from your Web server, you will maybe need to set some CORS headers. So in the server side, replace:

const httpServer  = http.createServer();
const httpsServer = https.createServer({
    "key" : fs.readFileSync( "yourcert.key" ),
    "cert": fs.readFileSync( "yourcert.crt" ),
    "ca"  : fs.readFileSync( "yourca.crt"   )
});

With:

const CORS_fn = (req, res) => {
    res.setHeader( "Access-Control-Allow-Origin"     , "*"    );
    res.setHeader( "Access-Control-Allow-Credentials", "true" );
    res.setHeader( "Access-Control-Allow-Methods"    , "*"    );
    res.setHeader( "Access-Control-Allow-Headers"    , "*"    );
    if ( req.method === "OPTIONS" ) {
        res.writeHead(200);
        res.end();
        return;
    }
};
const httpServer  = http.createServer( CORS_fn );
const httpsServer = https.createServer({
        "key" : fs.readFileSync( "yourcert.key" ),
        "cert": fs.readFileSync( "yourcert.crt" ),
        "ca"  : fs.readFileSync( "yourca.crt"   )
}, CORS_fn );

And of course add/remove headers and set the values of the headers according to your needs.

Sapient answered 7/8, 2019 at 14:12 Comment(0)
P
4

check this.configuration..

app = module.exports = express();
var httpsOptions = { key: fs.readFileSync('certificates/server.key'), cert: fs.readFileSync('certificates/final.crt') };        
var secureServer = require('https').createServer(httpsOptions, app);
io = module.exports = require('socket.io').listen(secureServer,{pingTimeout: 7000, pingInterval: 10000});
io.set("transports", ["xhr-polling","websocket","polling", "htmlfile"]);
secureServer.listen(3000);
Philippa answered 25/10, 2016 at 13:0 Comment(0)
C
2

Server-side:

import http from 'http';
import https from 'https';
import SocketIO, { Socket } from 'socket.io';
import fs from 'fs';
import path from 'path';

import { logger } from '../../utils';

const port: number = 3001;

const server: https.Server = https.createServer(
  {
    cert: fs.readFileSync(path.resolve(__dirname, '../../../ssl/cert.pem')),
    key: fs.readFileSync(path.resolve(__dirname, '../../../ssl/key.pem'))
  },
  (req: http.IncomingMessage, res: http.ServerResponse) => {
    logger.info(`request.url: ${req.url}`);

    let filePath = '.' + req.url;
    if (filePath === './') {
      filePath = path.resolve(__dirname, './index.html');
    }

    const extname = String(path.extname(filePath)).toLowerCase();
    const mimeTypes = {
      '.html': 'text/html',
      '.js': 'text/javascript',
      '.json': 'application/json'
    };

    const contentType = mimeTypes[extname] || 'application/octet-stream';

    fs.readFile(filePath, (error: NodeJS.ErrnoException, content: Buffer) => {
      if (error) {
        res.writeHead(500);
        return res.end(error.message);
      }
      res.writeHead(200, { 'Content-Type': contentType });
      res.end(content, 'utf-8');
    });
  }
);

const io: SocketIO.Server = SocketIO(server);

io.on('connection', (socket: Socket) => {
  socket.emit('news', { hello: 'world' });
  socket.on('updateTemplate', data => {
    logger.info(data);
    socket.emit('updateTemplate', { random: data });
  });
});

server.listen(port, () => {
  logger.info(`Https server is listening on https://localhost:${port}`);
});

Client-side:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Websocket Secure Connection</title>
</head>

<body>
  <div>
    <button id='btn'>Send Message</button>
    <ul id='messages'></ul>
  </div>
  <script src='../../../node_modules/socket.io-client/dist/socket.io.js'></script>
  <script>
    window.onload = function onload() {
      const socket = io('https://localhost:3001');
      socket.on('news', function (data) {
        console.log(data);
      });

      socket.on('updateTemplate', function onUpdateTemplate(data) {
        console.log(data)
        createMessage(JSON.stringify(data));
      });
      const $btn = document.getElementById('btn');
      const $messages = document.getElementById('messages');

      function sendMessage() {
        socket.emit('updateTemplate', Math.random());
      }

      function createMessage(msg) {
        const $li = document.createElement('li');
        $li.textContent = msg;
        $messages.appendChild($li);
      }

      $btn.addEventListener('click', sendMessage);
    }
  </script>
</body>

</html>
Caitlyncaitrin answered 5/9, 2018 at 6:50 Comment(1)
But how do you make port 3001 secure? Only 443 is secure on my VPS. When I try and use 3001, I get a red polling link in Chrome Dev tools. This is because the port is insecure.Ingravescent
G
2

For enterprise applications it should be noted that you should not be handling https in your code. It should be auto upgraded via IIS or nginx. The app shouldn't know about what protocols are used.

Galvin answered 16/7, 2019 at 14:45 Comment(0)
A
2

In case someone need a shorter form

var fs = require('fs');
var https = require('https');

var express = require('express');
var app = express();

var options = {
  key: fs.readFileSync('/path-to/ssl.key'),
  cert: fs.readFileSync('/path-to/ssl.cert')
};

var server = https.createServer(options, app);
var io = require('socket.io')(server);
Auscultation answered 10/6, 2021 at 5:19 Comment(0)
B
1

This is my nginx config file and iosocket code. Server(express) is listening on port 9191. It works well: nginx config file:

server {
    listen       443 ssl;
    server_name  localhost;
    root   /usr/share/nginx/html/rdist;

    location /user/ {
        proxy_pass   http://localhost:9191;
    }
    location /api/ {
        proxy_pass   http://localhost:9191;
    }
    location /auth/ {
        proxy_pass   http://localhost:9191;
    }

    location / {
        index  index.html index.htm;
        if (!-e $request_filename){
          rewrite ^(.*)$ /index.html break;
        }
    }
    location /socket.io/ {
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass   http://localhost:9191/socket.io/;
    }


    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    ssl_certificate /etc/nginx/conf.d/sslcert/xxx.pem;
    ssl_certificate_key /etc/nginx/conf.d/sslcert/xxx.key;

}

Server:

const server = require('http').Server(app)
const io = require('socket.io')(server)
io.on('connection', (socket) => {
    handleUserConnect(socket)

  socket.on("disconnect", () => {
   handleUserDisConnect(socket)
  });
})

server.listen(9191, function () {
  console.log('Server listening on port 9191')
})

Client(react):

    const socket = io.connect('', { secure: true, query: `userId=${this.props.user._id}` })

        socket.on('notifications', data => {
            console.log('Get messages from back end:', data)
            this.props.mergeNotifications(data)
        })

Brynhild answered 30/3, 2019 at 10:9 Comment(0)
A
0

Server side:

var ssl_options = {
    ca: [fs.readFileSync('../ssl/cert1.crt'), fs.readFileSync('../ssl/cert2.crt'), fs.readFileSync('../ssl/cert3.crt')],
    key: fs.readFileSync('../ssl/xxx.key'),
    cert: fs.readFileSync('../ssl/xxx.example.crt'),
};
var wssServer = https.createServer(ssl_options,app);  // Express app
wssServer.listen(4433, '0.0.0.0');
global.io = require("socket.io")();
io.listen(wssServer);
io.on( "connection", function( socket ) {
    console.log( "user connected" );
});

Client-side (no luck with the built-in WebSocket API):

<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.3/socket.io.js"></script>
<script>
   const socket = io("https://xxx.example:4433",{ transports: ['websocket', 'polling', 'flashsocket'] });
</script>
Abraham answered 14/3, 2021 at 11:19 Comment(0)
P
0

I needed to get this to work with Debian 10, ISPConfig 3 and Let's Encrypt. Took me a while to work out the specifics. Maybe this saves someone else some time…

Server-side:

const fs = require('fs');
const https = require('https');
const express = require('express');
const socketio = require('socket.io');
const app = express();
const https_options = {
    key: fs.readFileSync('/var/www/clients/client1/web1/ssl/your-domain.com-le.key'),
    cert: fs.readFileSync('/var/www/clients/client1/web1/ssl/your-domain.com-le.crt'),
    ca: fs.readFileSync('/root/.acme.sh/your-domain.example/fullchain.cer'),
    requestCert: false,
    rejectUnauthorized: false
}
const server = https.createServer(https_options, app);
server.listen(3000, () => {
    console.log('server started ok');
});
const io = socketio(server, {
    cors: {
        origin: "https://your-domain.example",
    },
    secure: true
});
io.on('connection', (sock) => {
    console.log('someone connected');
}

Client-side:

const sock = io('https://your-domain.example:3000/');
sock.on('status', (text) => {
    add_text($('#status'), text);
});
Pus answered 16/9, 2021 at 10:6 Comment(0)
R
0

I got it working like this:

const server = require("https");
const { Server } = require("socket.io");

const io = new Server({
    cors: {
      origin: "*",
    },
  });

  const folder = path.join(__dirname, "ssl");
  const privateKey = fs.readFileSync(
    path.join(folder, "server_key.pem"),
    "utf8"
  );
  const certificate = fs.readFileSync(
    path.join(folder, "server_cert.pem"),
    "utf8"
  );

  const SSL_CONFIG= {
    key: privateKey,
    cert: certificate,
    ca: [certificate],
    requestCert: false,certificate, tested and it works
    rejectUnauthorized: false,
  };

  const WEB_SERVER = server.createServer(SSL_CONFIG);

  WEB_SERVER.listen(1102);
  io.listen(WEB_SERVER);
Raptor answered 24/8, 2023 at 9:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.