socket.io-client how to set request header when making connection - node.js

I'm trying to set a http header when socket.io client makes the connection request. Is there a way to do this?
Here is what i'm doing:
// server side
var io = socketio(server);
io.use(function (socket, next) {
// authorize using authorization header in socket.request.headers
});
// client side
var socket = io(); // i'm trying to set an authorization header in this http reqeust
Any ideas? Thanks.

You can use extraHeaders option, if you are using socket.io-client >= 1.4.
For example:
var socket = io("http://localhost", {
extraHeaders: {
Authorization: "Bearer authorization_token_here"
}
});
engine.io-client, which is a backend of socket.io-client, introduced extraHeaders support on 2015-11-28.

It seems like the client doesn't support setting headers, as not all transports allow for the setting of headers.
This post by facundoolano details a workaround to authentication that doesn't require placing the auth token in the query string.
His workaround module can be found at https://github.com/invisiblejs/socketio-auth.
Makes me wonder why on server-side, socket.io allows for the request headers to be accessed...

There's a new way to do this: https://socket.io/docs/v3/middlewares/. Look under the "Sending Credentials" section.
// client
const socket = io(server, {
transports: ['websocket', 'polling', 'flashsocket'],
auth: {
token: 'abc'
}
});
// server
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (isValidJwt(token)){
next();
}else{
next(new Error("Socket authentication error"));
}
});
async function isValidJwt(token){
jwt.verify(token, secrets.jwt, function(err, decoded) {
if (err){
console.log(err);
return false;
}else{
//console.log(decoded);
return true;
}
});
}

This following information has been deprecated since socket.io 1.0
There are two methods of authorization: global or namespace (think route). The global method is set on the server with the io.set('authorization', function (handshakeData, callback) configuration call.
The handshakeData object contains the following information:
{
headers: req.headers // <Object> the headers of the request
, time: (new Date) +'' // <String> date time of the connection
, address: socket.address() // <Object> remoteAddress and remotePort object
, xdomain: !!headers.origin // <Boolean> was it a cross domain request?
, secure: socket.secure // <Boolean> https connection
, issued: +date // <Number> EPOCH of when the handshake was created
, url: request.url // <String> the entrance path of the request
, query: data.query // <Object> the result of url.parse().query or a empty object
}
The above information and a deeper explanation is available on this documentation page.

As of version 2.0.0 / 2017-01-22 engine.io-client supports
[feature] Allow extraHeaders to be set for browser clients in XHR requests (#519)
However at this point the socket.io-client is not updated to support this functionality, so couple of days may make this saga end until that time use the following instructions: https://facundoolano.wordpress.com/2014/10/11/better-authentication-for-socket-io-no-query-strings/

"transportOptions" options can be used to send extra headers in socket.io request. I also explained that here :-
Node.js + Socket.io | Set custom headers on the server

For some reason, these request headers are only received if the socket server is also socket.io.
If I connect to a python Websockets server for example I have no luck authenticating.
The only solution that worked for me is to use a different WebSocket client, for example, ws works fine.
import WebSocket from 'ws';
const socket = new WebSocket('wss://example.com/path', {
headers: {
Authorization: 'token'
},
});

Short Answer: It's imposiburu based on spec... if you just need to pass info early... why not query parameters?
socket = io('localhost:5000', {
path: '/mySocketPath',
transports: ['websocket'],
query: {
token:'some-token-value'
}
})
See #satpal-07 in
https://github.com/socketio/socket.io-client/issues/1356#issuecomment-810023635

Related

extraHeaders doesnt work in socket.io-client

Hi my code looks like this in nodejs, it works!
but when i try to use exactly same code in nwjs app, it doesn't! because as i sniff and see it thinks it runs under a web browser and it tries to use native functions of nwjs's chromium browser.
function connecttest(domain)
{
socket = require('socket.io-client')('https://ws.'+domain.replace('www.').replace('m.'),{
forceNode:true,
transports: ['websocket'],
extraHeaders: {
'Origin': 'https://www.'+domain.replace('www.').replace('m.')
},
transportOptions: {
polling: {
extraHeaders: {
'Origin': 'https://www.'+domain.replace('www.').replace('m.')
}
}
}
});
socket.on('connect', function(socket){
console.log("ok");
socket.emit('query', {"body":{"siteId":"9","source":1,"lang":"tr"},"action":"site.session","frontEndId":0,"token":null});
socket.on('response', function(data){
console.log(data);
});
});
}
😒
as you see it can not set a right origin header
but it works under plain - nodejs script
Perhaps it is your transports: ['websocket']. Look here, at the official documentation:
With extraHeaders
This only works if polling transport is enabled
(which is the default). Custom headers will not be appended when using
websocket as the transport. This happens because the WebSocket
handshake does not honor custom headers. (For background see the
WebSocket protocol RFC)

How do you handle CORS in an electron app?

I'm building an electron app and need to call APIs where the API provider has not enabled CORS. The typically proposed solution is to use a reverse proxy which is trivial to do when running locally by using node and cors-anywhere like this:
let port = (process.argv.length > 2) ? parseInt (process.argv[2]) : 8080;
require ('cors-anywhere').createServer ().listen (port, 'localhost');
The app can then be configured to proxy all requests through the reverse proxy on localhost:8080.
So, my questions are:
Is it possible to use node and cors-anywhere in an electron app to create a reverse proxy? I don't want to force the app to make calls to a remote server.
Is there a better or standard way of doing this in an Electron app? I'm assuming I'm not the first to run into CORS issues. :)
Just overide header before send request using webRequest.onBeforeSendHeaders
const filter = {
urls: ['*://*.google.com/*']
};
const session = electron.remote.session
session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
details.requestHeaders['Origin'] = null;
details.headers['Origin'] = null;
callback({ requestHeaders: details.requestHeaders })
});
put these codes in renderer process
In my application, it wasn't sufficient to remove the Origin header (by setting it to null) in the request. The server I was passing the request to always provided the Access-Control-Allow-Origin header in the response, regardless of it the Origin header is present in the request. So the embedded instance of Chrome did not like that the ACAO header did not match its understanding of the origin.
Instead, I had to change the Origin header on the request and then restore it on the Access-Control-Allow-Origin header on the response.
app.on('ready', () => {
// Modify the origin for all requests to the following urls.
const filter = {
urls: ['http://example.com/*']
};
session.defaultSession.webRequest.onBeforeSendHeaders(
filter,
(details, callback) => {
console.log(details);
details.requestHeaders['Origin'] = 'http://example.com';
callback({ requestHeaders: details.requestHeaders });
}
);
session.defaultSession.webRequest.onHeadersReceived(
filter,
(details, callback) => {
console.log(details);
details.responseHeaders['Access-Control-Allow-Origin'] = [
'capacitor-electron://-'
];
callback({ responseHeaders: details.responseHeaders });
}
);
myCapacitorApp.init();
});
Try this if you are running web apps in localhost
const filter = {
urls: ['http://example.com/*'] // Remote API URS for which you are getting CORS error
}
browserWindow.webContents.session.webRequest.onBeforeSendHeaders(
filter,
(details, callback) => {
details.requestHeaders.Origin = `http://example.com/*`
callback({ requestHeaders: details.requestHeaders })
}
)
browserWindow.webContents.session.webRequest.onHeadersReceived(
filter,
(details, callback) => {
details.responseHeaders['access-control-allow-origin'] = [
'capacitor-electron://-',
'http://localhost:3000' // URL your local electron app hosted
]
callback({ responseHeaders: details.responseHeaders })
}
)
Just had this issue today API calls with axios inside a React app bundled in Electron is returning 400
From what I can see Electron calls act as normal calls to the API urls meaning they are not affected by CORS.
Now when you wrap your calls with a CORS proxy and make a regular call to the proxy, it should error with a 400 error because it's not a CORS call.
This thread explains why cors-anywhere responds like that => https://github.com/Rob--W/cors-anywhere/issues/39
I actually removed my CORS proxies from the app before the Electron build. I still need the CORS proxy for development since I'm testing in the browser.
Hope this helps.
You can have the main process, the NodeJS server running Electron, send the request. This avoids CORS because this is a server-to-server request. You can send an event from the frontend (the render process) to the main process using IPC. In the main process you can listen to this event, send the HTTP request, and return a promise to the frontend.
In main.js (the script where the Electron window is created):
import { app, protocol, BrowserWindow, ipcMain } from ‘electron’
import axios from 'axios'
ipcMain.handle('auth', async (event, ...args) => {
console.log('main: auth', event, args) const result = await axios.post(
'https://api.com/auth',
{
username: args[0].username,
password: args[0].password,
auth_type: args[1],
},
) console.log('main: auth result', result)
console.log('main: auth result.data', result.data) return result.data
})
In your frontend JS:
import { ipcRenderer } from 'electron'
sendAuthRequestUsingIpc() {
return ipcRenderer.invoke('auth',
{
username: AuthService.username,
password: AuthService.password,
},
'password',
).then((data) => {
AuthService.AUTH_TOKEN = data['access_token']
return true
}).catch((resp) => console.warn(resp))
}
I wrote an article that goes into more depth here.
While I have struggled a while with the existing answers I will provide here the solution that finally worked for me, assuming that you are on the main process.
Here are the steps involved:
You need to have access to the session object which can be obtained by one of two ways:
A) via the global session.defaultSession which is available after the app is ready.
const { session } = require('electron');
const curSession = session.defaultSession;
B) The other method is via the session on the BrowserWindow, this assumes that the windnows is already created.
win = new BrowserWindow({});
const curSession = win.webContents.session;
Once you have the session object you set the response header to the site you are sending the request from.
For example, let's say your electron BrowserWindow is loaded from http://localhost:3000 and you are making a request to example.com, here would be some sample code:
const { app, BrowserWindow, session } = require('electron');
app.whenReady().then(_ => {
// If using method B for the session you should first construct the BrowserWindow
const filter = { urls: ['*://*.example.com/*'] };
session.defaultSession.webRequest.onHeadersReceived(filter, (details, callback) => {
details.responseHeaders['Access-Control-Allow-Origin'] = [ 'http://localhost:3000' ];
callback({ responseHeaders: details.responseHeaders });
}
// Construct the BrowserWindow if haven't done so yet...
});
Have you tried using fetch()
Check how to use fetch to make a no-cors request here
https://developers.google.com/web/updates/2015/03/introduction-to-fetch?hl=en

How to get socket connect options in server? [duplicate]

So I have an application running node js with socket.io as a backend and normal javascript as frontend. My application has a login system which currently simply has the client send its login data as soon as it's connected.
Now I figured it would be much nicer to have the login data sent along with the handshakeData, so I can directly have the user logged in while connecting (instead of after establishing a connection) respectively refuse authorization when the login data is invalid.
I'm thinking it would be best to put my additional data in the header part of the handshakeData, so any ideas how I could do that? (Without having to modify socket.io if possible, but if it's the only way I can live with it)
As a lot of comments have pointed out below the Socket.IO API changed in their 1.0 release. Authentication should now be done via a middleware function, see 'Authentication differences' # http://socket.io/docs/migrating-from-0-9/#authentication-differences. I'll include my orginal answer for anyone stuck on <1.0 as the old docs seem to be gone.
1.0 and later:
Client Side:
//The query member of the options object is passed to the server on connection and parsed as a CGI style Querystring.
var socket = io("http://127.0.0.1:3000/", { query: "foo=bar" });
Server Side:
io.use(function(socket, next){
console.log("Query: ", socket.handshake.query);
// return the result of next() to accept the connection.
if (socket.handshake.query.foo == "bar") {
return next();
}
// call next() with an Error if you need to reject the connection.
next(new Error('Authentication error'));
});
Pre 1.0
You can pass a query: param in the second argument to connect() on the client side which will be available on the server in the authorization method.
I've just been testing it. On the client I have:
var c = io.connect('http://127.0.0.1:3000/', { query: "foo=bar" });
On the server:
io.set('authorization', function (handshakeData, cb) {
console.log('Auth: ', handshakeData.query);
cb(null, true);
});
The output on the server then looked like:
:!node node_app/main.js
info - socket.io started
Auth: { foo: 'bar', t: '1355859917678' }
Update
3.x and later
You can pass an authentication payload using the auth param as the second argument to connect() in the client side.
Client Side:
io.connect("http://127.0.0.1:3000/", {
auth: {
token: "AuthToken",
},
}),
In server side you can access it using socket.handshake.auth.token
Server Side:
io.use(function(socket, next){
console.log(socket.handshake.auth.token)
next()
});
This has now been changed in v1.0.0. See the migration docs
basically,
io.set('authorization', function (handshakeData, callback) {
// make sure the handshake data looks good
callback(null, true); // error first, 'authorized' boolean second
});
becomes :
io.use(function(socket, next) {
var handshakeData = socket.request;
// make sure the handshake data looks good as before
// if error do this:
// next(new Error('not authorized');
// else just call next
next();
});
For socket.io v1.2.1 use this:
io.use(function (socket, next) {
var handshake = socket.handshake;
console.log(handshake.query);
next();
});
This my code for sending query data to nodejs and server.io server client.
var socket = io.connect(window.location.origin, { query: 'loggeduser=user1' });
io.sockets.on('connection', function (socket) {
var endp = socket.manager.handshaken[socket.id].address;
console.log("query... " + socket.manager.handshaken[socket.id].query.user);
}
Perhaps the api has changed but I did the following to get extra info to the server.
// client
io.connect('localhost:8080', { query: 'foo=bar', extra: 'extra'});
// server
io.use(function(sock, next) {
var handshakeData = sock.request;
console.log('_query:', handshakeData._query);
console.log('extra:', handshakeData.extra);
next();
});
prints
_query: { foo: 'bar',
EIO: '3',
transport: 'polling',
t: '1424932455409-0' }
extra: undefined
If anyone knows how to get data from a client to the server through the handshake that is not in the query params let me know please.
Update I ran into issues later with this syntax
io.connect('localhost:8080?foo=bar');
is what I'm currently using.
Old thread but assuming you store your jwt token/session id in session cookies (standard stuff) this gets passed to the server by default anyway when doing handshake (socket.io-client) I've noticed.
Is there anything wrong with just getting the auth information for the handshake (via middleware or on.connection) via cookie?
eg.
io.on('connection', function(socket) {
// assuming base64url token
const cookieStr = socket.handshake.headers.cookie
const matchRes =
cookieStr == null
? false
: cookieStr.match(/my-auth-token=([a-zA-Z0-9_.-]+)/)
if (matchRes) {
// verify your jwt...
if ( tokenIsGood(matchRes[1]) {
// handle authenticated new socket
} else {
socket.emit('AUTH_ERR_LOGOUT')
socket.disconnect()
}
} else {
socket.emit('AUTH_ERR_LOGOUT')
socket.disconnect()
}
}
I'm using this now for a project and it's working fine.
I found a little problem to see the .loggeduser
io.sockets.on('connection', function (socket) {
var endp = socket.manager.handshaken[socket.id].address;
console.log("query... " + socket.manager.handshaken[socket.id].query.loggeduser);
// ↑ here
}

How do I stream data to browsers with Hapi?

I'm trying to use streams to send data to the browser with Hapi, but can't figure our how. Specifically I am using the request module. According to the docs the reply object accepts a stream so I have tried:
reply(request.get('https://google.com'));
The throws an error. In the docs it says the stream object must be compatible with streams2, so then I tried:
reply(streams2(request.get('https://google.com')));
Now that does not throw a server side error, but in the browser the request never loads (using chrome).
I then tried this:
var stream = request.get('https://google.com');
stream.on('data', data => console.log(data));
reply(streams2(stream));
And in the console data was outputted, so I know the stream is not the issue, but rather Hapi. How can I get streaming in Hapi to work?
Try using Readable.wrap:
var Readable = require('stream').Readable;
...
function (request, reply) {
var s = Request('http://www.google.com');
reply(new Readable().wrap(s));
}
Tested using Node 0.10.x and hapi 8.x.x. In my code example Request is the node-request module and request is the incoming hapi request object.
UPDATE
Another possible solution would be to listen for the 'response' event from Request and then reply with the http.IncomingMessage which is a proper read stream.
function (request, reply) {
Request('http://www.google.com')
.on('response', function (response) {
reply(response);
});
}
This requires fewer steps and also allows the developer to attach user defined properties to the stream before transmission. This can be useful in setting status codes other than 200.
2020
I found it !! the problem was the gzip compression
to disable it just for event-stream you need provide the next config to Happi server
const server = Hapi.server({
port: 3000,
...
mime:{
override:{
'text/event-stream':{
compressible: false
}
}
}
});
in the handler I use axios because it support the new stream 2 protocol
async function handler(req, h) {
const response = await axios({
url: `http://some/url`,
headers: req.headers,
responseType: 'stream'
});
return response.data.on('data',function (chunk) {
console.log(chunk.toString());
})
/* Another option with h2o2, not fully checked */
// return h.proxy({
// passThrough:true,
// localStatePassThrough:true,
// uri:`http://some/url`
// });
};

Send custom data along with handshakeData in socket.io?

So I have an application running node js with socket.io as a backend and normal javascript as frontend. My application has a login system which currently simply has the client send its login data as soon as it's connected.
Now I figured it would be much nicer to have the login data sent along with the handshakeData, so I can directly have the user logged in while connecting (instead of after establishing a connection) respectively refuse authorization when the login data is invalid.
I'm thinking it would be best to put my additional data in the header part of the handshakeData, so any ideas how I could do that? (Without having to modify socket.io if possible, but if it's the only way I can live with it)
As a lot of comments have pointed out below the Socket.IO API changed in their 1.0 release. Authentication should now be done via a middleware function, see 'Authentication differences' # http://socket.io/docs/migrating-from-0-9/#authentication-differences. I'll include my orginal answer for anyone stuck on <1.0 as the old docs seem to be gone.
1.0 and later:
Client Side:
//The query member of the options object is passed to the server on connection and parsed as a CGI style Querystring.
var socket = io("http://127.0.0.1:3000/", { query: "foo=bar" });
Server Side:
io.use(function(socket, next){
console.log("Query: ", socket.handshake.query);
// return the result of next() to accept the connection.
if (socket.handshake.query.foo == "bar") {
return next();
}
// call next() with an Error if you need to reject the connection.
next(new Error('Authentication error'));
});
Pre 1.0
You can pass a query: param in the second argument to connect() on the client side which will be available on the server in the authorization method.
I've just been testing it. On the client I have:
var c = io.connect('http://127.0.0.1:3000/', { query: "foo=bar" });
On the server:
io.set('authorization', function (handshakeData, cb) {
console.log('Auth: ', handshakeData.query);
cb(null, true);
});
The output on the server then looked like:
:!node node_app/main.js
info - socket.io started
Auth: { foo: 'bar', t: '1355859917678' }
Update
3.x and later
You can pass an authentication payload using the auth param as the second argument to connect() in the client side.
Client Side:
io.connect("http://127.0.0.1:3000/", {
auth: {
token: "AuthToken",
},
}),
In server side you can access it using socket.handshake.auth.token
Server Side:
io.use(function(socket, next){
console.log(socket.handshake.auth.token)
next()
});
This has now been changed in v1.0.0. See the migration docs
basically,
io.set('authorization', function (handshakeData, callback) {
// make sure the handshake data looks good
callback(null, true); // error first, 'authorized' boolean second
});
becomes :
io.use(function(socket, next) {
var handshakeData = socket.request;
// make sure the handshake data looks good as before
// if error do this:
// next(new Error('not authorized');
// else just call next
next();
});
For socket.io v1.2.1 use this:
io.use(function (socket, next) {
var handshake = socket.handshake;
console.log(handshake.query);
next();
});
This my code for sending query data to nodejs and server.io server client.
var socket = io.connect(window.location.origin, { query: 'loggeduser=user1' });
io.sockets.on('connection', function (socket) {
var endp = socket.manager.handshaken[socket.id].address;
console.log("query... " + socket.manager.handshaken[socket.id].query.user);
}
Perhaps the api has changed but I did the following to get extra info to the server.
// client
io.connect('localhost:8080', { query: 'foo=bar', extra: 'extra'});
// server
io.use(function(sock, next) {
var handshakeData = sock.request;
console.log('_query:', handshakeData._query);
console.log('extra:', handshakeData.extra);
next();
});
prints
_query: { foo: 'bar',
EIO: '3',
transport: 'polling',
t: '1424932455409-0' }
extra: undefined
If anyone knows how to get data from a client to the server through the handshake that is not in the query params let me know please.
Update I ran into issues later with this syntax
io.connect('localhost:8080?foo=bar');
is what I'm currently using.
Old thread but assuming you store your jwt token/session id in session cookies (standard stuff) this gets passed to the server by default anyway when doing handshake (socket.io-client) I've noticed.
Is there anything wrong with just getting the auth information for the handshake (via middleware or on.connection) via cookie?
eg.
io.on('connection', function(socket) {
// assuming base64url token
const cookieStr = socket.handshake.headers.cookie
const matchRes =
cookieStr == null
? false
: cookieStr.match(/my-auth-token=([a-zA-Z0-9_.-]+)/)
if (matchRes) {
// verify your jwt...
if ( tokenIsGood(matchRes[1]) {
// handle authenticated new socket
} else {
socket.emit('AUTH_ERR_LOGOUT')
socket.disconnect()
}
} else {
socket.emit('AUTH_ERR_LOGOUT')
socket.disconnect()
}
}
I'm using this now for a project and it's working fine.
I found a little problem to see the .loggeduser
io.sockets.on('connection', function (socket) {
var endp = socket.manager.handshaken[socket.id].address;
console.log("query... " + socket.manager.handshaken[socket.id].query.loggeduser);
// ↑ here
}

Resources