NodeJS, Express, why should I use app.enable('trust proxy'); - javascript

I was needed to redirect http to https and found this code:
app.enable('trust proxy');
app.use((req, res, next) => {
if (req.secure) {
next();
} else {
res.redirect('https://' + req.headers.host + req.url);
}
});
I'm using heroku to host my project, I noticed that heroku as default issued *.herokuapp.com cert, so I can use http and https as well.
When looked at req.secure within app.use callback, without app.enable('trust proxy'), req.secure is always false, when I add app.enable('trust proxy') it's false for about 2 times and after the https redirection it's switches to true.
app.enable('trust proxy'), the docs:
Indicates the app is behind a front-facing proxy, and to use the
X-Forwarded-* headers to determine the connection and the IP address
of the client.
My question:
Why would my server be behind a proxy?(is it relates to the issued *.herokuapp.com cert?), if someone could explain how all fits together, I mean, why my server is behind a proxy? and why without app.enable express won't identify(or accept) secure connection?

If your not running behind a proxy, it's not required. Eg, if your running multiple websites on a server, chances are your using a Proxy.
X-Forwarded-For header attributes get added when doing this so that your proxy can see what the original url was, proxying in the end will be going to localhost you see. The reason why it's needed is that X-Forwared-For can be faked, there is nothing stopping the client adding these too, not just a proxy. So trust-proxy should only be enabled on the receiving end, that would be behind your firewall. Because you have control, you can trust this.
So in a nutshell, if your website is running behind a proxy, you can enable it. If you website is running direct on port 80, you don't want to trust it. As the sender could pretend to be coming from localhost etc.

Related

Node.js Restrict HTTP server to be accessible only on 127.0.0.1

I have created a ws websocket server in nodejs and a http server that is listening on a specific port and host is 127.0.0.1. WebSocket server connection is established on the upgrade event emitted by the http server.
For security purpose I want the server to be accessible only on localhost/127.0.0.1 and not from 0.0.0.0 IP.
Example:
ws://0.0.0.0:5050 - Should not accept connections
ws://127.0.0.1:5050 - Should accept connections
How can I restrict the server to be only reachable from localhost and not from any other IP(including 0.0.0.0)?
const server = http.createServer();
const wss = new WebSocket.Server({ noServer: true });
server.listen(5050, '127.0.0.1');
server.on('upgrade', function (request, socket, head) {
wss.handleUpgrade(request, socket, head, function (ws) {
//conditional checks
wss.emit('connection', ws, request);
})
})
Can somebody please direct me to the proper way of doing this.
if(req.socket.remoteAddress !== '127.0.0.1'){
res.send('403 Access Denied');
res.end();
} else {
// allow access
}
0.0.0.0 isn't an IP address. It is a non-routable meta-address used to designate an invalid, unknown or non-applicable target.
You might have a client that treats requests to 0.0.0.0 as being intended for localhost, but that isn't a security problem since your server still won't be accessible outside of the current machine.
Your existing code, where you only listen on 127.0.0.1 is sufficient.
Access and security that you are talking about are supposed to be done at the firewall of your instance hosting nodejs server.
However, if you still want to implement it on your nodejs layer you can dig the request object there you will find the IP address from where the request is coming, and based on that you can take your action.
Hope it helps, Thank you

HTTPS Server in Node.js

I was wondering how can I create a https server in node listening on Port 443 in a way that when I type :
https://my.ip.address:443 it should work.
https://my.ip.address it should work.
my.ip.address (without the https://) it should work and redirect me to https)
my.ip.address:443 it should work and redirect me to https
So far I was only able to make the first and second url work.
So my question is how can I make it also work for the other two possibilities (the final two). Thanks
If you type my.ip.address into a browser's address bar then it will request http://my.ip.address:80. To get that to work with your SSL version you need to:
Listen for HTTP (not HTTPS) on port 80
Issue a 301 HTTP Redirect to the SSL site
If you type my.ip.address:443 into a browser, then it will request http://my.ip.address:443. This will try to make an HTTP request without setting up SSL first and get an error. There is nothing you can do about that.
You can make redirects from http to https.
Via nginx
https://github.com/vodolaz095/hunt/blob/master/examples/serverConfigsExamples/nginx.conf#L22-L39
server {
listen 80;
server_name example.org;
rewrite ^ https://$host$request_uri? permanent;
}
Via expressjs middleware
https://github.com/vodolaz095/hunt/blob/master/examples/index.js#L133-L139
something like this:
app.use(function (request, response, next) {
if (request.protocol === 'http') {
response.redirect('https://yourhostname.com' + request.originalUrl);
} else {
next();
}
});

How do I route sub domains in Node.js without Express?

Answers to this question explain how to route sub domains in Node.js with Express.
I want to know how to do it without Express.
Here's my server module, which returns a server object:
module.exports.serve = function(port) {
var server = https.createServer(options, function(req, res) {
// Parse & process URL
var reqInfo = url.parse(req.url, true, true),
path = reqInfo.pathname;
debug.log("Client [" + req.connection.remoteAddress +
"]requesting resource: " + path);
// Quickly handle preloaded requests
if (preloaded[path])
preloadReqHandler(req, res, preloaded[path], path);
// Handle general requests
else generalReqHandler(req, res, reqInfo);
}).listen(port);
return server;
};
No need to go into detail with the modules that handle the requests, I'm just interested in how to detect www.example.com and route it to example.com or vice-versa, via my server.
Just to add as much detail as possible, my goal here is to route all traffic from http://www.example.com and http://example.com and https://www.example.com and send it all to https://example.com. To do that, I think I just need to learn how to route the www sub domain, and then listen on both the http and https ports for that routing.
Since HTTP 1.1, user agents send the Host request header which specifies the domain. So you can get the domain (including the port if specified) from req.headers['host'] and apply your custom domain routing logic.
If you're talking with a HTTP 1.0 or older user agent, then just reply with "505 HTTP Version Not Supported" or serve some default content.

PassportJS callback switch between http and https

Right now, when I visit my page at https://example.com and click login, it goes to https://example.com/auth/facebook, which then does the facebook stuff and ends up calling back http://example.com/auth/facebook/callback. I can't seem to get it to call back using the https scheme (but only when the request cycle started in https).
right now, when viewed through an https iframe (facebook canvas app), I get the error
[blocked] The page at
'https://apps.facebook.com/example/?fb_source=notification&ref=notif&notif_t=app_notification'
was loaded over HTTPS, but ran insecure content from
'http://example.com/auth/facebook/callback?code=AQD5TUeTP…yXC0ZM8S45V2iTta629IaquCpAqVUbhAvNCFveaDBlbKg4J4#=':
this content should also be loaded over HTTPS.
passport.use(new FacebookStrategy({
clientID: process.env.FB_CLIENT,
clientSecret: process.env.FB_SECRET,
callbackURL: "/auth/facebook/callback",
profileFields: ['id']
},...
app.get('/auth/facebook',
passport.authenticate('facebook', {
scope: ["read_stream"]
})
);
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
failureRedirect: '/#'
}),
function(req, res) {
res.redirect('/#');
});
I'm running this on heroku, where it handles the details on https.
EDIT
Apparently node provides req.connection.encrypted with information as to whether the request is https. Since I am running on heroku behind nginx where that handles all of the https before node, req.connection.encrypted will always be undefined.
Still don't know how to solve this though.
I looked into the Passport Oauth2 strategy code and checked that it uses req.connection.encrypted to check if it is in a secure connection.
It also checks for proxies in case the server code runs behind one. It is possible to tell passport to trust a proxy if you know that you are behind one.
It seems that since SSL is handled by nginx on Heroku, req.connection.encrypted is always "undefined". (groups.google.com/forum/#!topic/express-js/Bm6yozgoDSY)
Nginx handles all of the HTTPS on Heroku so node never sees req.connection.encrypted being anything other than "undefined".
To solve the problem you have to tell passport to trust the proxy adding the line
app.enable("trust proxy");
to your express server.
I've also learned that we can accomplish the same thing by adding another property called "proxy:true" to the googleStrategy such as below:
passport.use(new GoogleStrategy({
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: '/auth/google/callback',
proxy: true }
Nginx handles all of the HTTPS on Heroku so node never sees req.connection.encrypted being anything other than undefined. By digging through the passportjs repositories I found that there is a check for the app to have "trust proxy" enabled. To solve this problem, add the line
app.enable("trust proxy");
to your express server.
I am sorry, this answer is kind of lame, I cannot really give you a specific answer based on the nature of the thing.
I believe that your callback url in the facebook api console is probably http, if you change it to https it should work.
If that does not work, you could try changing your callbackURL to a full url ("https://example.com/auth/facebook/callback")
Trying passport-facebook and passport-disqus I had the same problem, with Google there's no problem since it let you use http for callback but the others don't.
Setting "app.enable("trust proxy");" on main app file solved it!

Force SSL with expressjs 3

I'm running a node.js express 3 server with no proxies and using SSL.
I'm trying to figure out how to force all connections to go through https.
Google searching shows me this:
https://groups.google.com/forum/#!topic/express-js/Bm6yozgoDSY
There's currently no way to force https redirects, though that seems
like a bit of a strange work-around. We have an https-only app and we
just have a simple ~4 line node http server that redirects, nothing
fancy
Which is what I need, but he doesn't say what those 4 lines are.
How do we do this? Thanks.
I don't really understand the point in starting two servers when only one can do the job perfectly. For example, by adding a simple middleware in your server file:
app.use(function(req, res, next) {
if(!req.secure) {
return res.redirect(['https://', req.get('Host'), req.url].join(''));
}
next();
});
This will redirect any non-secure request to the corresponding HTTPS page. For example, http://example.com/ to https://example.com/ and http://example.com/foo?bar=woo to https://example.com/foo?bar=woo. This is definitely the behavior I would expect. Maybe you should filter this by host, so it redirects only on domains for which you own and installed a proper certificate.
If your app is running behind another server like Nginx, you may want to add the configuration parameter app.set('trust proxy', true). Or, even better, make Nginx do the redirect itself, which will be more efficient than any Node.js app.
Edit: According to my benchmarks, join is a little faster than + for concatenating strings. Nothing dramatic, but every win is a win...
You should create a second server listening on 80 and redirect with a 301 header to your https server:
var express = require('express');
var app = express();
app.get('/', function(req, res, next){
res.redirect('https://' + app.address().address)
});
app.listen(80);
I had a similar problem and the redirect solution is not suitable for me because essentially I want to get rid of the browser's insecure warning,
So instead of redirect every message, I did:
app1 = new express()
app1.get('/', function(req, res) {
res.sendFile(path.join(__dirname + '/redirect.html'));
});
app1.listen(80, function(){'redirect server running on 80 port'})
and in the redirect.html is just a redirecting html file:
<meta http-equiv="refresh" content="0; URL='https://my-site.com'" />
Of course, this won't work for complicated cases when you want to redirect all routings, but for me, I only want to redirect my homepage to my https homepage and get rid of the browser's insecure warning. Hope this help somebody!

Categories