I have a webapp that communicates to Node.js Express server using websocket.
When verifying the websocket connection, I check the ORIGIN header of the request (and a few other parameters to ensure they are legitimate)
The expected request is either "https://www.mywebsite.com" or "https://mywebsite.com"
If the ORIGIN header is not expected, we will kick the user.
Then I noticed some people can be kicked when their socket connection looks alright, but the ORIGIN is "http://mywebsite.com". We quickly checked and realise the website can be visited in http. We added a piece of redirect code like this:
const server = express()
.enable('trust proxy')
.use((req, res, next) => {
req.secure ? next() : res.redirect('https://' + req.headers.host + req.url)
})
And now theoretically, whoever visit the http version of the website should be redirected to https.
But, even this redirection is done, we still notice people being kicked because their origin is http instead of https. Why is this so? Is there any chance that some users can never use https?
This is the correct way to redirect to https on Heroku:
Under the hood, Heroku router (over)writes the X-Forwarded-Proto and the X-Forwarded-Port request headers. The app must check X-Forwarded-Proto and respond with a redirect response when it is not https but http.
Taken from: https://help.heroku.com/J2R1S4T8/can-heroku-force-an-application-to-use-ssl-tls
This is some sample code you can use:
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`)
} else {
next()
}
})
The reason your code doesn't work is that Heroku does SSL termination for you and serves the certificates this means the connection between the Heroku router and your Node.js server is insecure and req.secure returns false:
https://devcenter.heroku.com/articles/http-routing#routing
Correction: Cause you set trust proxy this means req.protocol will be set to https and req.secure will return true so your code will work.
Related
I have nodejs backend server which runs in port 5000. Currently it supports both http and https requests.
For me its not a problem because my client applications (frontend applications) uses https requests always. But someone else can send http request using (postman) like apps any time.
But I want to know is this secure ? or do I need to convert each http request to https ? If yes how ?
Edit 1 ->
If I need to redirect I can use below code inside server.js,
if (process.env.NODE_ENV === 'production') {
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https')
res.redirect(`https://${req.header('host')}${req.url}`)
else next()
})
}
But if I use this hacker can send x-forwarded-proto as https but the url as http , in that case also response will be http.
So what would be the best way to redirect http to https ?
I'm trying to restrict the origin of CORS requests to one specific domain per route using the express.js CORS package like so:
const express = require('express');
const cors = require('cors');
const port = process.env.PORT || 3000;
let app = express();
app.get('/', cors({origin: 'http://example.com'}), (req, res, next) => {
res.sendStatus(200);
});
app.post('/', cors({origin: 'http://whatever.com'}) (req, res, next) => {
res.sendStatus(200);
});
app.listen(port, () => {
console.log(`Started on port ${port}`);
});
This doesn't seem to have any effect, however, as I'm able to GET and POST from any domain. I then tried instead to restrict all routes to one single origin using the following, but met the same results:
app.use(cors({origin: 'http://example.com'}));
I'm experiencing this both in my dev environment on localhost and my production environment on Heroku. Any idea what I'm missing?
If your server is sending an Access-Control-Allow-Origin: http://example.com response header, then you actually already have it configured correctly.
It’s expected behavior for the server to return a 200 response no matter what origin you make the request from—even for those from an origin other than the configured http://example.com
The CORS settings don’t cause the server to block requests from any clients.
Instead, if the server responds with Access-Control-Allow-Origin: http://example.com to a client request from JavaScript code in a web app that’s not running at http://example.com, then the browser blocks that JavaScript code from being able to access the response.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS gives more details.
Basically the way it works is that from the server side, no behavior changes other than the difference in what response headers it sends. So the server will receive the request just as it otherwise would, and will send the response just as it otherwise would.
And then the browser will receive the response just as it otherwise would. You will be able to see the response in your browser devtools and examine it there. But that does not mean the browser will expose the response to your client-side JavaScript code.
Instead, the browser checks the value of the Access-Control-Allow-Origin response header from the server and will only expose the response cross-origin to your origin if the server says it should be allowed to: Your browser checks the value of the Access-Control-Allow-Origin against your actual origin, and if it either matches exactly or the value is * to allow any origin, only then does the browser allow your client-side JavaScript code to access the response.
I wrote an express app as an HTTP proxy, to intercept and analyse some of the network traffic. The parts of traffic my app is interested in are all HTTP, however I still want my app to proxy HTTPS so users can use it without extra setting.
My express app is created with a HTTP server. When testing, I changed the proxy setting in Chrome with SwitchyOmega, to proxy HTTPS connections with HTTP. HTTP works well, But my express app couldn't get these proxy requests for HTTPS.
So I wrote a simple TCP proxy to check on them, and find that they're like this:
CONNECT HOSTNAME:443 HTTP/1.1
Host: HOSTNAME
Proxy-Connection: keep-alive
User-Agent: MY_AGENT
ENCRYPTED HTTPS
I believe these requests are HTTP, but why express isn't receiving them?
For sure if I change the browser proxy setting to ignore HTTPS, the app works well. But I do want to know if there is any workaround that I can use to proxy all protocols with HTTP and only one port.
THX.
UPDATE- code from my express app
app.use('*', function (req, res, next) {
// print all the request the app receive
console.log('received:', req.url)
})
app.use(bodyParser.text({type: '*/*'}))
app.use(cookieParser())
app.use(logger('dev'))
app.use(express.static(path.join(__dirname, 'public')))
// serve web pages for my app, only the request targeting my server
// is handled here(right IP and port), proxy request gets handled after this.
app.use('/', internalRoute)
// analyse the part I want
app.use('/END_POINT_I_WANT', myRoute)
// handle proxy requests
app.use('*', function (req, res, next) {
// proxy the request here
})
The problem is, my first middleware, which is used to display all the requests the app receive, can't catch the HTTPS proxy requests wrapped in HTTP described above. And of course the middleware I used as proxy can't catch them either.
UPDATE-tried node-http-prxoy, no luck
var httpProxy = require('http-proxy')
, http = require('http')
, fs = require('fs')
var options = {target: 'http://127.0.0.1:8099'}
, proxy = httpProxy.createServer(options)
http.createServer(function (req, res) {
console.log(req.url)
proxy.web(req, res)
}).listen(5050)
With the above code, and browser setting to proxy all protocols with HTTP, it works the same as my express app. HTTPS proxy requests gets ERR_EMPTY_RESPONSE, and nothing on the console.
With the below options, it seems that I have to change the proxy protocol to HTTPS, which I'd rather not use, at least for now. And I get ERR_PROXY_CERTIFICATE_INVALID for my self-signed certs...
var options = { secure: true
, target: 'http://127.0.0.1:8099'
, ssl: { key: fs.readFileSync('cert/key.pem', 'utf8')
, cert: fs.readFileSync('cert/server.crt', 'utf8')
}
}
UPDATE- pin point the problem to the 'connect' event listener
Through some searching, I found this post helpful.
It pointed out that the http server doesn't have a listener for the connect event. I tried the code in the post, works. But as the last comment of that post mentioned, my app serves as a proxy in order to get the data, it then proxy the request to another proxy in order to go over the GreatFireWall.
The process is like : BROWSER -> MY_APP -> ANOTHER_PROXY -> TARGET.
Without the ANOTHER_PROXY, which is an HTTP proxy, it works well for both HTTP and HTTPS. However I failed to chain them all up. The ANOTHER_PROXY I use supports HTTPS over HTTP.
It's hard to see what might be wrong, since you haven't posted any code.
However, if you just want to create a simple proxy that supports HTTP and HTTPS, i think that you should consider using a module like node-http-proxy.
Their readme has example code for the most common scenarios, and it sounds like it will support your needs fine.
We have a node.js express application running in Heroku. It handles authentication and has to be a highly secure.
We have forced redirect to HTTPS when we get HTTP request. But this does not seem to be enough. With tools like sslstrip we can POST via HTTP.
The only solution at hand seems to be disable the HTTP completely on Heroku.
How to do that? Is there any other suggestions?
According to OWASP you should not redirect from HTTP to HTTPS. See https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-REMOVED-_Do_Not_Perform_Redirects_from_Non-TLS_Page_to_TLS_Login_Page for more details.
I think the better solution would be to reject the request with a message letting the user know why. You should be able to do this in a middleware function. The actual status code you return is debatable but something like this should work:
app.use(function(req, res, next) {
if(req.protocol !== 'https') {
return res.status(403).send({message: 'SSL required'});
}
// allow the request to continue
next();
});
You can test whether a request used https and then force a redirect using https if that is required (note the concern pointed out by #Ryan about redirecting and security). With Heroku, you can check the req headers' x-forwarded-proto header to make sure it is https. Here is an example:
var express = require('express');
var env = process.env.NODE_ENV || 'development';
var forceSSL = function (req, res, next) {
if (req.headers['x-forwarded-proto'] !== 'https') {
return res.redirect(['https://', req.get('Host'), req.url].join(''));
}
return next();
};
var app = express();
// in your app-level configurations
if (env === 'production') app.use(forceSSL);
Note: the Heroku load balancers are determining the x-forwarded-proto header before it hits your app.
Also: get an SSL certificate if you are using a custom domain with Heroku
We've used express-sslify npm package. Added
app.use(enforce.HTTPS(true));
I want force certain routes to always use a secure connection in my express app. How can I check to make sure it is using https?
I am using piggyback ssl on heroku for my deployments.
I deploy on Heroku as well. They add a bunch of their headers when they use nginx to reverse proxy. The one of interest in this case would be x-forwarded-proto.
This is what I did:
app.get(/\/register$/, function(req, res){
console.log(JSON.stringify(req.headers)); //to see all headers that heroku adds
if(req.headers['x-forwarded-proto'] && req.headers['x-forwarded-proto'] === "http") {
res.redirect("https://" + req.headers.host + req.url);
}
else {
//the rest of your logic to handle this route
}
});
app.enable('trust proxy');
"Using Express behind a reverse proxy such as Varnish or Nginx is trivial, however it does require configuration. By enabling the "trust proxy" setting via app.enable('trust proxy'), Express will have knowledge that it's sitting behind a proxy and that the X-Forwarded-* header fields may be trusted, which otherwise may be easily spoofed."
Express behind proxies doco
In order to run a secure server (https) it would have to be created independently from a non-secure server (http). They would also listen on separate ports. Try something like this:
var express = require('express)
, app_insecure = express.createServer()
, app_secure = express.createServer({ key: 'mysecurekey' })
app_insecure.get('/secure-page',function(req, res){
// This is an insecure page, redirect to secure
res.redirect('https://www.mysecuresite.com/secure-page')
})
app_secure.get('/secure-page', function(req,res){
// Now we're on a secure page
})
app_insecure.listen(80)
app_secure.listen(443)
OR this could be implemented as route middleware
var redirect_secure = function(req, res, next){
res.redirect('https://mysite.com' + req.url)
}
app_insecure.get('/secure-page',redirect_secure,function(req, res){})
Now you would only have to include the function reference: redirect_secure() on the paths that you would like redirected to a secure location.