Socket.io in production returns 400 Bad Request error - socket.io

I am using Socket.IO in my application. The React client uses socket.io-client 4.1.3, and the Node.js server uses socket.io 4.1.3
In the development environment on my local machine, everything works fine.
The React app runs on http://localhost:3000, and connects to the server using:
import io from 'socket.io-client';
const socket = io('http://localhost:5000/');
The Node.js server is configured as below:
const express = require('express');
const app = express();
const server = require('http').createServer(app);
const cors = require('cors');
const io = require('socket.io')(server, {
cors: {
origin: 'http://localhost:3000'
},
maxHttpBufferSize: '1e6'
});
app.set('io', io);
app.use(express.static('public'));
app.use(express.json({ limit: '7mb' }));
app.use(cors({ origin: 'http://localhost:3000' }));
server.listen(5000, () => console.log('Server started'));
In production, I am using Firebase to host the React app, in a subdirectory (e.g. https://www.example.com/app/).
In production, http://localhost:5000/ and http://localhost:3000 in the code above have also been changed to https://app.example.com and https://www.example.com/app respectively.
My server uses Ubuntu 20.04, Nginx, and Let's Encrypt, with a server block set up as follows:
server {
server_name app.example.com;
location / {
proxy_pass http://localhost:5000/;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}server {
if ($host = app.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name app.example.com;
return 404; # managed by Certbot
}
In Google Chrome, I was getting No 'Access-Control-Allow-Origin' header is present on the requested resource as an error. Changing the origin from https://www.example.com/app to * in the Node.js code fixed this.
However, now I am getting the following error in my browser:
POST https://app.example.com/socket.io/?EIO=4&transport=polling&t=NirW_WK&sid=PmhwTyHRXOV4jWOdAAAF 400 (Bad Request)
Why would this be?
Thanks

A few small changes to both the Node.js and Nginx should resolve your problem:
Node.js
First off, I'd recommend that you change this:
cors: {
origin: 'http://localhost:3000'
},
to this (as specified here):
cors: {
origin: 'http://localhost:3000',
methods: ["GET", "POST"]
},
Nginx
Change this:
location / {
proxy_pass http://localhost:5000/;
}
to this:
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
proxy_pass http://localhost:5000/;
}
This post here can help give more information on CORS headers needed in Nginx reverse proxies

Try adding a variable port to access an environment variable. PORT should be set to https://app.example.com/
const port = process.env.PORT || 3000
And use it everywhere that local host 3000 was used in your backend code.
This should also help
const io = require("socket.io")(server, {
cors: {
origin: port,
methods: ["GET", "POST"],
allowedHeaders: ['Access-Control-Allow-Origin'],
credentials: false
}
})

I was facing same issue and backend was on aws elasticbeanstalk, so we set Load Balancer, to handle multiple request calls and this error was fixed. So i think you need to check cloud base function for load balancing.

Related

Handling CORS in Nginx as a reverse proxy

We encountered a very weird behavior when using Nginx as a reverse proxy.
We have REST services that need to be used with the browser, We configured these HTTP Golang services to handle CORS using cors package.
We noticed that when the browser triggers a preflight request the request doesn't go to the backend service and instead Nginx responds with the status 405 Method Not Allowed and the request is never forwarded or proxied to the backend service at all.
To fix this issue we had to handle OPTIONS requests manually from the location directive, This is very limited as we need the liberty to update for example the allowed headers using Access-Control-Allow-Headers based on the requested route.
How can we forward any Preflight or OPTIONS request to the backend to handle?
NOTE: We are using default Nginx configurations according to the official Nginx docker image.
file: main.go
package main
import (
"net/http"
"github.com/rs/cors"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
})
r := cors.AllowAll().Handler(mux)
http.ListenAndServe(":3000", r)
}
file: server.conf
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
# Handling OPTIONS for this location directive.
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "$http_origin";
add_header Access-Control-Allow-Credentials 'true';
add_header Access-Control-Allow-Headers 'Authorization, Content-Type, Origin, X-Requested-With, Accept';
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE';
add_header Content-Type 'text/plain; charset=utf-8';
return 204;
}
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-Remote-Addr $remote_addr;
proxy_set_header X-Original-Host $host;
}
}
Related:
Handling OPTIONS request in nginx
Cross Origin preflight request in Nginx Proxy

Setting redirect when accessing Cognito via sk-auth

I have built a Svelte application using SvelteKit that uses Cognito for authentication. I used the following site: Cognito authentication for your SvelteKit app guide me in setting this up. The app and connection to Cognito works well when running in local development via npm run dev, however, when running in production on an EC2 server via npm run build and pm2 start /build/index.js it sets the redirect_uri portion of the Cognito URI to http://localhost:3000. I can't figure out how to get it to set the redirect to my actual domain.
Here are some relevant code snippets on how it is currently set up on EC2:
/etc/nginx/sites-available/domain.conf
server {
server_name example.com;
location / {
root /var/www/html/build;
proxy_pass http://localhost:3000;
}
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
svelte.config.js
import node from '#sveltejs/adapter-node';
/** #type {import('#sveltejs/kit').Config} */
const config = {
kit: {
target: '#svelte',
adapter: node({
out: 'build',
precompress: false,
env: {
host: 'example.com',
port: '443'
}
})
}
};
export default config;
/src/lib/auth.js
import { SvelteKitAuth, Providers } from 'sk-auth';
const DOMAIN = 'myapi.auth.us-east-1.amazoncognito.com';
const config = {
accessTokenUrl: `https://${DOMAIN}/oauth2/token`,
profileUrl: `https://${DOMAIN}/oauth2/userInfo`,
authorizationUrl: `https://${DOMAIN}/oauth2/authorize`,
redirect: 'https://example.com',
clientId: myAWSclientID,
clientSecret: myAWSclientSecret,
scope: ['openid', 'email'],
id: 'cognito',
contentType: 'application/x-www-form-urlencoded'
};
const oauthProvider = new Providers.OAuth2Provider(config);
export const appAuth = new SvelteKitAuth({
providers: [oauthProvider]
});
Expected URL when going to Cognito
https://myapi.auth.us-east-1.amazoncognito.com/login?state=cmVkaXJlY3Q9Lw%3D%3D&nonce=699&response_type=code&client_id=myAWSclientID&scope=openid+email&redirect_uri=https%3A%2F%2Fexample.com%2Fapi%2Fauth%2Fcallback%2Fcognito%2F
Actual URL when going to Cognito
https://myapi.auth.us-east-1.amazoncognito.com/login?state=cmVkaXJlY3Q9Lw%3D%3D&nonce=699&response_type=code&client_id=myAWSclientID&scope=openid+email&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fcallback%2Fcognito%2F
As you can see, it is attempting to set the redirect_uri to http://localhost:3000 instead of the expected https://example.com. I'm pretty sure that there is some setting somewhere to allow it to set the correct redirect_uri when going to Cognito - any ideas or suggestions would be appreciated!
From what I can tell looking at the sk-auth module source code, redirect_uri doesn't appear to be a valid config option. Try setting the host config option in the global SkAuth constructor instead:
const config = {
accessTokenUrl: `https://${DOMAIN}/oauth2/token`,
profileUrl: `https://${DOMAIN}/oauth2/userInfo`,
authorizationUrl: `https://${DOMAIN}/oauth2/authorize`,
// redirect_uri: 'https://example.com',
clientId: myAWSclientID,
clientSecret: myAWSclientSecret,
scope: ['openid', 'email'],
id: 'cognito',
contentType: 'application/x-www-form-urlencoded'
};
.
.
export const appAuth = new SvelteKitAuth({
providers: [oauthProvider],
host: 'https://example.com',
});
After further browsing the source, you can also set the redirect option provided by the AuthCallbacks interface on the provider configuration:
const config = {
accessTokenUrl: `https://${DOMAIN}/oauth2/token`,
profileUrl: `https://${DOMAIN}/oauth2/userInfo`,
authorizationUrl: `https://${DOMAIN}/oauth2/authorize`,
// redirect_uri: 'https://example.com',
redirect: 'https://example.com',
clientId: myAWSclientID,
clientSecret: myAWSclientSecret,
scope: ['openid', 'email'],
id: 'cognito',
contentType: 'application/x-www-form-urlencoded'
};
which, incidentally, is what the author uses in the tutorial you referred to.

Golang app behind Nginx reverse proxy won't accept ajax request on firefox due to CORS

So I have a domain name which is a static html file that sends an ajax request to a subdomain app which is behind Nginx in a reverse proxy.
Here is my ajax code:
$(document).ready(function(){
function call() {
$.ajax({
type: "POST",
url: "https://test.example.com/call",
crossDomain: true,
data: $("#form-call").serialize(),
success: function(response) {
$("#response").html(response);
},
error: function() {
alert("error");
}
});
And on Nginx I have:
upstream example {
server 192.168.1.10:6000;
}
server {
listen 443;
server_name test.example.com;
ssl on;
ssl_certificate /etc/nginx/ssl/afs.crt;
ssl_certificate_key /etc/nginx/ssl/afs.key;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Authorization "";
client_max_body_size 0;
chunked_transfer_encoding on;
add_header 'Access-Control-Allow-Origin' "$http_origin";
location / {
proxy_pass https://exapmle;
proxy_read_timeout 900;
}
}
And I have this on my golang app to help with CORS:
func (s *MyServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if origin := req.Header.Get("Origin"); origin != "" {
rw.Header().Set("Access-Control-Allow-Origin", origin)
rw.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
rw.Header().Set("Access-Control-Allow-Headers",
"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
}
// Stop here if its Preflighted OPTIONS request
if req.Method == "OPTIONS" {
return
}
// Lets Gorilla work
s.r.ServeHTTP(rw, req)
}
It works fine on chrome, but on firefox I get the error:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://test.example.com/call. This can be fixed by moving the resource to the same domain or enabling CORS.
DISCLAIMER: I have just seen how old this post is, I imagine the problem has already been solved.
This worked for me:
func corsHandler(fn http.HandleFunc) http.HandleFunc {
return func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Access-Control-Allow-Origin", "*")
fn(rw, req)
}
}
Then when setting up your routers you can:
r := mux.NewRouter()
r.HandleFunc("/<route>", corsHandler(handleRouteMethod)).Methods("<METHOD>")
Slightly different approach but it has definitely worked, so if all else fails, you could give it a try (this assumes you are using gorilla mux, if you are using the httprouter or something else then your corsHandler method maybe needs to use a different function signature).

How do I get socket.io running for a subdirectory

I've got a proxy running that only hits my node.js server for paths that being with /mysubdir
How do I get socket.io configured for this situation?
In my client code I tried:
var socket = io.connect('http://www.example.com/mysubdir');
but then I notice that the underlying socket.io (or engine.io) http requests are hitting
http://www.example.com/socket.io/?EIO=3&transport=polling&t=1410972713498-72`
I want them to hit
http://www.example.com/mysubdir/socket.io.....
Is there something I have to configure on the client and the server?
In my server I had to
var io = require('socket.io')(httpServer, {path: '/mysubdir/socket.io'})`
In my client I had to
<script src="http://www.example.com/mysubdir/socket.io/socket.io.js"></script>
and also
var socket = io.connect('http://www.example.com', {path: "/mysubdir/socket.io"});`
In my case I am using nginx as a reverse proxy. I was getting 404 errors when polling. This was the solution for me.
The url to the node server is https://example.com/subdir/
In the app.js I instantiated the io server with
var io = require('socket.io')(http, {path: '/subdir/socket.io'});
In the html I used
socket = io.connect('https://example.com/subdir/', {
path: "/subdir"
});
Cheers,
Luke.
Using nginx, this a solution without the need to change anything in the socket.io server app:
In the nginx conf:
location /mysubdir {
rewrite ^/mysubdir/(.*) /socket.io/$1 break;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://127.0.1.1:3000;
}
In the server:
var io = require('socket.io')(3000)
In the client:
var socket = io.connect('https://example.com/', {
path: "/mysubdir"
})
The answer by #Drew-LeSueur is correct for socket.io >= 1.0.
As I was using socket.io 0.9, I found the old way of doing it in the doc.
// in 0.9
var socket = io.connect('localhost:3000', {
'resource': 'path/to/socket.io';
});
// in 1.0
var socket = io.connect('localhost:3000', {
'path': '/path/to/socket.io';
});
Notice that a / appears as first character in the new path option.

Can't disable same origin policy on nginx

I need to disable the same origin policy on the server. Just as a background: I have verified that everything is working by starting chrome with the disable web security flag. Everything works as expected.
Here's what I have done on the nginx side:
upstream phpfcgi {
server unix:/var/run/php5-fpm.sock; #for PHP-FPM running on UNIX socket
}
server {
listen 80;
root /var/www/yammi2;
index index.html index.php index.htm;
server_name myserver.ch;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials 'true';
add_header Access-Control-Allow-Headers 'Content-Type,accept,x-wsse,origin';
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE';
# strip app.php/ prefix if it is present
rewrite ^/app\.php/?(.*)$ /$1 permanent;
location / {
index app.php;
try_files $uri #rewriteapp;
}
location #rewriteapp {
rewrite ^(.*)$ /app.php/$1 last;
}
# pass the PHP scripts to FastCGI server from upstream phpfcgi
location ~ ^/(app|app_dev|config)\.php(/|$) {
fastcgi_pass phpfcgi;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS off;
}
}
When I then do curl call: curl -I myserver.ch, I get the following result:
HTTP/1.1 302 Found
Server: nginx/1.1.19
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/5.3.10-1ubuntu3.9
Set-Cookie: PHPSESSID=gvcl3v533ib91l2c6v888gl9d3; path=/
cache-control: no-cache
date: Fri, 10 Jan 2014 07:01:18 GMT
location: http://myserver.ch/admin/restaurant
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type,accept,x-wsse,origin
Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE
so at least it seems that the headers are set correctly, yet the result when I make the ajax call:
OPTIONS http://myserver.ch/api/v1/restaurant/closest?max=50&lat=47&lon=8 500 (Internal Server Error) jquery-2.0.3.js:7845
OPTIONS http://myserver.ch/api/v1/restaurant/closest?max=50&lat=47&lon=8 Origin http://localhost is not allowed by Access-Control-Allow-Origin. jquery-2.0.3.js:7845
XMLHttpRequest cannot load http://myserver.ch/api/v1/restaurant/closest?max=50&lat=47&lon=8. Origin http://localhost is not allowed by Access-Control-Allow-Origin. overview.html:1
I'm a bit confused by the "Internal Server Error", but I figured since it works with the flag, this has to be something to do with same origin.
The server application is a symphony app. I hope I haven't missed anything. Any idea how to fix this? Or even how to debug it?
Maybe one last snipped, here is how I make the call (again, shouldn't be the issue, because with the disable security flag it works as expected):
$.ajax({
url: url,
headers: {"x-wsse": getWsseHeader()},
beforeSend: function (request) {
request.setRequestHeader("x-wsse", getWsseHeader());
},
success: function() {
},
error: function(error) {
console.log(error.statusText);
}
});
Change this line
add_header Access-Control-Allow-Origin *;
as
add_header 'Access-Control-Allow-Origin' '';

Resources