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));
Related
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.
As per the title, I have a Node.js application and I want to be able to detect whether a request is being made over HTTPS or HTTP. So far my redirection looks like this:
// Ensure the page is secure, or that we are running a development build
if (req.headers['x-forwarded-proto'] === 'https' || process.env.NODE_ENV === 'development') {
res.render('index');
} else {
winston.info('Request for login page made over HTTP, redirecting to HTTPS');
res.redirect('https://' + req.host);
}
Which works fine on Nodejitsu, but a redirected HTTPS request doesn't have the 'x-forwarded-proto' header set on Azure.
I think I was correct in my comment:
X-ARR-SSL seems to be the header to check for.
// Ensure the page is secure, or that we are running a development build
if (req.headers['x-forwarded-proto'] === 'https' || req.headers['x-arr-ssl'] || process.env.NODE_ENV === 'development') {
res.render('index');
} else {
winston.info('Request for login page made over HTTP, redirecting to HTTPS');
res.redirect('https://' + req.host);
}
Ran into the exact same issue, but solved it in a different way. If you're using Express and enable trust proxy then req.protocol will pick up the x-forwarded-proto header. Azure doesn't set the x-forwarded-proto header, but you can use the x-arr-ssl header to hack it in manually so that req.protocol will return the correct value in the rest of your app.
Here's a gist: https://gist.github.com/freshlogic/6348417
var express = require('express');
var app = express();
app.enable('trust proxy');
// HACK: Azure doesn't support X-Forwarded-Proto so we add it manually
app.use(function(req, res, next) {
if(req.headers['x-arr-ssl'] && !req.headers['x-forwarded-proto']) {
req.headers['x-forwarded-proto'] = 'https';
}
return next();
});
UPDATE-2021:
This is a very old answer. For a long time there is an option on all app service plans that support Custom domains. Go to the Custom domains blade in the azure portal for the App Service and set the HTTPS only checkbox. This will redirect even before traffic hits the app service.
Using Node/Express in a Heroku hosted app. I have an http to https forwarding function, which had worked but no longer.
I had forwarding working with the following from my app.configure('production'...:
app.configure('production', function() {
return app.use(function(req, res, next) {
if (req.header('x-forwarded-proto') !== 'https') {
return res.redirect("https://" + (req.header('host')) + req.url);
} else {
return next();
}
});
It worked, I was satisfied and moved on to implement Redis-to-go instead of MemoryStorage and then to implement csrf middleware. Both of those features are working but now I'm finding that I can access my app through http://... whereas before that would have been caught and auto-forwarded to the https://... and the friendly green padlock.
Any idea what could have gone sour in the interim?
Self-answer:
Somewhere during the csrf implementation process, I made the following change to my app.configure section:
app.use(app.router);
I'd read this usage in the csrf demonstrations. But I don't know if it's necessary. Csrf middleware functionality seems un-affected when I commented that line out, and the Heroku redirect works as before.
As per the title, I have a Node.js application and I want to be able to detect whether a request is being made over HTTPS or HTTP. So far my redirection looks like this:
// Ensure the page is secure, or that we are running a development build
if (req.headers['x-forwarded-proto'] === 'https' || process.env.NODE_ENV === 'development') {
res.render('index');
} else {
winston.info('Request for login page made over HTTP, redirecting to HTTPS');
res.redirect('https://' + req.host);
}
Which works fine on Nodejitsu, but a redirected HTTPS request doesn't have the 'x-forwarded-proto' header set on Azure.
I think I was correct in my comment:
X-ARR-SSL seems to be the header to check for.
// Ensure the page is secure, or that we are running a development build
if (req.headers['x-forwarded-proto'] === 'https' || req.headers['x-arr-ssl'] || process.env.NODE_ENV === 'development') {
res.render('index');
} else {
winston.info('Request for login page made over HTTP, redirecting to HTTPS');
res.redirect('https://' + req.host);
}
Ran into the exact same issue, but solved it in a different way. If you're using Express and enable trust proxy then req.protocol will pick up the x-forwarded-proto header. Azure doesn't set the x-forwarded-proto header, but you can use the x-arr-ssl header to hack it in manually so that req.protocol will return the correct value in the rest of your app.
Here's a gist: https://gist.github.com/freshlogic/6348417
var express = require('express');
var app = express();
app.enable('trust proxy');
// HACK: Azure doesn't support X-Forwarded-Proto so we add it manually
app.use(function(req, res, next) {
if(req.headers['x-arr-ssl'] && !req.headers['x-forwarded-proto']) {
req.headers['x-forwarded-proto'] = 'https';
}
return next();
});
UPDATE-2021:
This is a very old answer. For a long time there is an option on all app service plans that support Custom domains. Go to the Custom domains blade in the azure portal for the App Service and set the HTTPS only checkbox. This will redirect even before traffic hits the app service.
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.