I have a typical web application in Node that is utilizing the Express framework and the session middleware. I am also using Socket.io for certain dynamic parts of my application (currently, this is a chat mechanism, but that's tangential). I've been able to successfully set up sessions and socket.io on their own, but would like to combine them (EG: to associate socket chat messages with user accounts without hitting the database).
It should be noted (and I can see this being a possible issue point), I am running two express servers on different ports: one for regular HTTP traffic, and one for HTTPS traffic. However, I am having both servers undergo an idential configuration and share the same session store. Sessions do persist for me between http and https pages. The session is being set initially via a page served from HTTPS and the socket.io page is vanilla HTTP.
I'm following the guide located here to achieve what I am looking for regarding integrating socket.io and sessions. However, within the authorization function, data.headers.cookie is never set, despite the session-based portions of my application working as expected. What's more strange is that after setting a session, if I do a console.log(document.cookie) from within the browser, I get an empty string, but when I look at my cookies with the Firefox developer toolbar, there is an SID cookie for both express and connect.
Here is the relevant portion of the server code:
var config = {
ip : "127.0.0.1",
httpPort : 2031,
httpsPort : 2032
};
var utils = require("./utils"),
express = require('express'),
fs = require('fs'),
parseCookie = require('./node_modules/express/node_modules/connect').utils.parseCookie,
routes = require('./routes')(config);
var httpsOpts = {
key : fs.readFileSync("cert/server-key.pem").toString(),
cert: fs.readFileSync("cert/server-cert.pem").toString()
};
var app = express.createServer(),
https = express.createServer(httpsOpts),
io = require("socket.io").listen(app, { log: false}),
helpers = require("./helpers.js"),
session = new express.session.MemoryStore(),
sessionConfig = express.session({
store : session,
secret : 'secret',
key : 'express.sid',
cookie : {maxAge : 60 * 60 * 1000}
}); //share this across http and https
configServer(app);
configServer(https);
//get SID for using sessions with sockets
io.set('authorization', function(data, accept){
if(data.headers.cookie){
data.cookie = parseCookie(data.headers.cookie);
data.sessionID = data.cookie['express.sid'];
} else {
return accept("No cookie transmitted", false);
}
accept(null, true);
});
io.sockets.on('connection', function(socket){
//pull out session information in here
});
function configServer(server) {
server.configure(function(){
server.dynamicHelpers(helpers.dynamicHelpers);
server.helpers(helpers.staticHelpers);
server.set('view options', { layout: false });
server.set('view engine', 'mustache');
server.set('views', __dirname + '/views');
server.register(".mustache", require('stache'));
server.use(express.static(__dirname + '/public'));
server.use(express.bodyParser());
server.use(express.cookieParser());
server.use(sessionConfig);
});
}
And here's the relevant code on the client:
<script src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
$(document).ready(function(){
var socket = io.connect('http://127.0.0.1'); //make sure this isn't localhost!
socket.on('server', function(data){
//socket logic is here
});
}
</script>
UPDATE
Even after setting a cookie manually (and not just a session variable) in the route for the page that is using SocketIO, the cookies portion of the request is still absent.
I never would have thought of this until told to look at the initialization on the client side. I changed the address from localhost to the explicit IP (127.0.0.1) and the cookies are now being sent with the header in Socket.IO. I'm not sure if this is obvious or not, as I assumed localhost was being mapped to 127.0.0.1 anyway.
Related
First a quick preface I think may be helpful: I am new to splitting my client and server into separate web apps. My previous apps have all had my server.js at the root level in my directory and the client (typically a create-react-app) in a /client folder.
What I wanted to do: Host a single express.js server on the web which multiple other web applications could make requests to.
I did the first part using an express server and aws elastic beanstalk.
server.js
require('dotenv').config()
const express = require('express');
const app = express();
const cors = require('cors');
const PORT = process.env.PORT || 5000;
const Mongoose = require('mongoose');
const { Sequelize } = require("sequelize");
//ROUTES
const APIUser = require('./routes/api/mongo/api-user');
more routes...
//INITIATE DATA MAPPING CONNECTIONS START
Mongoose.connect(
process.env.MONGO_URI,
{ useNewUrlParser: true, useUnifiedTopology: true },
console.log("connected to MongoDB")
);
const Postgres = new Sequelize(process.env.PG_CONN_STRING);
try {
Postgres.authenticate()
.then(console.log("connected to Postgres"));
} catch {
console.log("Postgres connection failed")
}
//INITIATE DATA MAPPING CONNECTIONS END
//middleware
app.use(cors())
more middleware...
//home route
app.get('/api', (req, res) => {
console.log('RECEIVED REQ to [production]/api/')
res.status(200).send('models api home').end();
})
//all other routes
app.use('/api/user', APIUser);
more route definitions...
//launch
app.listen(PORT, () => console.log(`listening on port ${PORT}`));
The log file for successful boot up on aws: https://imgur.com/vLgdaxK
At first glance it seemed to work as my postman requests were working. Status 200 with appropriate response: https://imgur.com/VH4eHzH
Next I tested this from one of my actual clients in localhost. Here is one of my react client's api util files where axios calls are made to the backend:
import { PROXY_URL } from '../../config';
import { axiosInstance } from '../util';
const axiosProxy = axios.create({baseURL: PROXY_URL}); //this was the most reliable way I found to proxy requests to the server
export const setAuthToken = () => {
const route = "/api/authorization/new-token";
console.log("SENDING REQ TO: " + PROXY_URL + route)
return axiosProxy.post(route)
}
export const authorize = clientsecret => {
const route = "/api/authorization/authorize-survey-analytics-client";
console.log("SENDING REQ TO: " + PROXY_URL + route)
return axiosProxy.post(route, { clientsecret })
}
Once again it worked... or rather I eventually got it to work: https://imgur.com/c2rPuoc
So I figured all was well now but when I tried using the live version of the client the request failed somewhere.
in summary the live api works when requests are made from postman or localhost but doesn't respond when requests are made from a live client https://imgur.com/kOk2RWf
I have confirmed that the requests made from the live client do not make it to the live server (by making requests from a live client and then checking the aws live server logs).
I am not receiving any Cors or Cross-Origin-Access errors and the requests from the live client to the live server don't throw any loud errors, I just eventually get a net::ERR_CONNECTION_TIMED_OUT. Any ideas where I can look for issues or is there more code I could share?
Thank you!
Add a console.log(PROXY_URL) in your client application and check your browser console if that's logged correctly.
If it works on your local client build, and through POSTMAN, then your backend api is probably good. I highly suspect that your env variables are not being set. If PROXY_URL is an emplty string, your axios requests will be routed back to the origin of your client. But I assume they have different origins since you mention that they're separate apps.
Remember environment variables need to prefixed with REACT_APP_ and in a production build they have to be available at build time (wherever you perform npm run build)
I'm must say I'm very new to back end development,
I'm currently working on an exercise project of making a fake money poker website. I use Node.js socket.io/express-session/passport
At first, I mainly used express with a HTTP server listening on one port. Averagely Like this:
const express = require("express")
const app = express()
app.get('/home',connectEnsureLogin.ensureLoggedIn("/loginPage"),function(req, res) {
//console.log(req.user.username+": sessionId: "+req.sessionID);
return res.sendFile( __dirname+"/website/index.html");
}
);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log("Poker site Server started on ${PORT})")
The website wasn't working very fast. When a client joined a poker table they needed to ask the server every second for new updates on the state of the game so that was a lot of HTTP requests coming into my server. So I decided without much theoretical certitude that it seemed like a good idea: To have the server use socket.io sockets to hand info for clients that are in poker tables, but when they are not in poker tables and are just browsing the site I use a HTTP server to handle their request. Code wise I feel I haven't really managed to do this correctly. My code with Express, express-session, and passport combined makes sure only to hand information to users authenticated. But since The socket.io servers seem totally separate from all the express code, they don't share the same authentication functionality as the express code. So I need to somehow link my express and socket.io code so I can check if a client is authenticated before handing him any info via sockets. here is the system I'm currently using I didn't put all my code but I tried to summarize the essential parts:
const express = require('express');
const app = express();
//i creat the http server that is somehow linked with my express app when this server is listening
//it will call express handling methods.
const http = require('http').Server(app);
const io = require('socket.io')(http);
const path = require("path");
const passport = require("passport");
const connectEnsureLogin = require('connect-ensure-login');
const AccountInfo = require("./AccountInfo").AcccountInfo;
const expressSession = require('express-session')({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false
});
//passport setup
passport.use(AccountInfo.createStrategy());
passport.serializeUser(AccountInfo.serializeUser());
passport.deserializeUser(AccountInfo.deserializeUser());
//body parser
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
//Sessions
app.use(expressSession);
//!!!!here is where I connect socket.io with the sessions i found this in another forum.
// thanks to this code I can access the session that a client is using when their socket connects.
io.use(function(socket, next) {
expressSession(socket.request, socket.request.res, next);
});
//so when a clients socket connects i save his socket.id to his session.
io.on('connection',function(socket) {
console.log(`socket.io connected: ${socket.id}`);
// save socket.io socket in the session
socket.request.session.socketio = socket.id;
socket.request.session.save();
});
//once the clients socket is connected directly after the clients sends a HTTP "PUT" request
//and this code answers it.
app.post('/Table/ConnectSocketToTable',Utilities.ensureLoggedIn(),function(req, res)
{
//I retrieve the socket using the socket.id I had saved in the session.
let socket = io.sockets.sockets.get(req.session.socketio);
let player = GetPlayerFromAnyTable(req.user.username);
if(player==null)//the player can't be in two tables at once
{
//since now we are in an express callback, express made sure that the client is indeed
//authenticated with the middle-ware: "Utilities.ensureLoggedIn()" also just before I made sure
//the client is not in another table. So we are good to go we can now link the socket to the table
//and have the client receive all the info about the state of his table
socket.join("table-"+req.session.passport.table);
req.user.socket = socket;
let table = GetTable(req.session.passport.table);
table.sitPlayer(req.user);
}
else
{
//the player is already connected so we just update his socket to a new one
player.requestUnseat=false;
player.account.socket =io.sockets.sockets.get(req.session.socketio);
}
socket.on('chatMessage', function(data,time) {
socket.to("table-"+req.session.passport.table).emit("chatMessage",req.user.username,data,time);
console.log(`send chat message : ${data}`);
});
socket.on('disconnect', function() {
GetTable(req.session.passport.table).requestUnsitUsername(req.user.username);
console.log(req.user.username +" was disconnected so now requesting unsit");
});
console.log("the socket of "+req.user.username+" has being connected to table-"+req.session.passport.table);
return res.sendStatus(200);
});
So for me, the way I'm doing this seems pretty bad since "app.post('/Table/ConnectSocketToTable'...)" and "io.on('connection',...)" are two different request listening functions I feel I should probably just do everything in one.
So should I do all the checks in the "io.on('connection',...)" function and somehow manage to make sure the client is authenticated within the callback of io.on('connection',callback) ?
or should I find a way to make the socket connection happen in the initial HTTP call the client uses to join a table, which is what I initially wanted?
But really I'm kinda lost because I'm telling myself maybe I don't even need Express anymore and I should just use socket.io for everything. I seem to clearly lack the general understanding that would allow me to know what approach I should be going for so any help is welcome. I started doing this self-made exercise to get into server-side development but also if there is any other recommended exercise to start up with back-end development I'm definitely interested in hearing about it.
From random testing I found out how to authenticate to my express session from the socket code you don't actually have to do it in the callback of io.on('connection',callback) you just need to add a few more middleware functions like this:
//connecting express sessions
io.use(function(socket, next) {
expressSession(socket.request, socket.request.res, next);
});
//connecting passport
io.use(function(socket, next) {
passport.initialize()(socket.request, socket.request.res, next);
});
//connecting passport sessions
io.use(function(socket, next) {
passport.session()(socket.request, socket.request.res, next);
});
//check if client is authenticated returns error if authentication failed
io.use((socket, next) => {
console.log("started socket Connection");
if(!socket.request.isAuthenticated&&socket.request.isAuthenticated())
{
socket.request.session.socketio = socket.id;
socket.request.session.save();
console.log("table "+socket.request.session.passport.table);
console.log("user.username "+socket.request.user.username);
console.log(`is authentificated`);
next();
}
else
{
console.log(`failed socket connection`);
next(new Error("unauthorized"));
}
});```
Here is the problem. When I load the page in the browser and check to see if my "test" was emitted, I run into this wall of spamming polling.
The code I use is exactly the same as in other projects I have done, so it makes no sense to me that this doesn't work now. -_-
app.js
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var port = process.env.PORT || 8000;
server.listen(port, function(){
console.log('server ready - listening on *:8000');
});
app.get( '/*' , function( req, res, next ) {
//This is the current file they have requested
var file = req.params[0];
//Send the requesting client the file.
res.sendFile( __dirname + '/' + file );
});
io.on('connection', function (socket) {
socket.on('test', function(){
console.log("test worked");
});
});
client.js
var socket = io();
socket.emit("test");
I broke the code down to what you see above. There's nothing else. And it doesn't work. internal screaming
I'll post my comment as an answer so you can wrap up this question. One common reason that socket.io will loop with http requests and never successfully connect is if you are running mismatched version on the client and server. This seems to have happened recently with socket.io upon a recent version change so they must have made some change in how the connection logic works that makes it fail to connect if versions are mismatched.
If you load your client via this:
<script src="/socket.io/socket.io.js"></script>
Then, the client will always match the server version as long as you don't have any other <script> tags that are loading some other version of socket.io.
One more solution which worked for me ( Socket.IO 2.3.0 and Socket.IO Client 2.3.0 ) is to set the transports field when you create the instance of io on back-end and socket on front-end , like this :
On back-end :
io = require('socket.io')(http,{
log: false,
agent: false,
origins : '*:*' , // this is for the CORS browser error , I also use the cors npm module here
transports : [ 'websocket' ]
});
And on front-end :
const socket = socketIOClient(url,{
forceNew : false ,
secure : true ,
transports: [ 'websocket' ]
});
Hope it helps , if not the question owner , then maybe the others :)
Consider, the following expressjs app:
var express = require('express');
var http = require('http');
var httpApp = express();
httpApp.configure(function() {
httpApp.use(express.static(__dirname + '/static/'));
});
var server = http.createServer(httpApp).listen(4444);
now,
i want this server not to be available publically & ONLY to respond to requests from specific clients based on their IP address/domain. Everybody else should get 403 - Forbidden error.
I searched the API Doc & found way to do this to first enable trust proxy by app.enable('trust proxy') & then check req.ip.
However, I can't I access req object. so if anyone take this code & can show me how deny a req based on its IP/domain, that would be super-helpful
Simply add a piece of middleware that checks the IP and denies access if it doesn't match:
app.use(function(req, res, next) {
if (allowed(req.ip))
next();
else
res.status(403).end('forbidden');
});
Use express-ipfilter
Installation
npm install express-ipfilter
Usage with Express
var express = require('express')
, ipfilter = require('express-ipfilter')
, app = express.createServer()
;
// Whitelist the following IPs
var ips = ['127.0.0.1'];
// Create the server
app.use(ipfilter(ips, {mode: 'allow'}));
app.listen(3000);
https://www.npmjs.com/package/express-ipfilter#installation
How to do socket.io implementation in Webrtc Video calling?
A little bit overload but it works: SocialVidRTC
I understand from your question that you already have a WebRTC project and some signalling mechanism in server.js , possibly websockets .
To replace this with socket.io or any other signalling as SIP / XHR / AJAX etc , you need to replace server.js with new socket.io based code for request and response .
Follow these steps :
create a https server ( since webrtc pages capture web cam input only from secure origins) for socket.io. Assign server to an variable say app.
var fs = require('fs');
var https = require('https');
var options = {
key: fs.readFileSync('ssl_certs/server.key'),
cert: fs.readFileSync('ssl_certs/server.crt'),
ca: fs.readFileSync('ssl_certs/ca.crt'),
requestCert: true,
rejectUnauthorized: false
};
var app = https.createServer(options, function(request, response){
request.addListener('end', function () {
file.serve(request, response);
}).resume();
});
app.listen(8081);
here server.key , server.crt and ca.crt are fake ssl certs and 8081 is the https port I have selected .
you can reuse the same https server for hosting the webpages also.
listen on this same port for socket.io using app defined earlier
var io = require('socket.io').listen(app, {
log: false,
origins: '*:*'
});
io.set('transports', [
'websocket'
]);
I choose only websocket but you can set other types of transport too such as
socket.set('transports', [
'websocket'
, 'flashsocket'
, 'htmlfile'
, 'xhr-polling'
, 'jsonp-polling'
]);
Now implement signalling specific functions and calls such as ,
io.sockets.on('connection', function (socket) {
...
socket.on('webrtc-joinchannel',function(data){
var resp=joinChannel(data);
socket.emit('resp-webrtc-joinchannel', resp);
});
...
});
Note : I am using socket.io v0.9 .
If yo want a example implementation you can view any sample projects such as here