I have a applications running on AWS Windows and Node.js. I can access using http and https. But i need it to forward http to https if anyone access through http.
I can think of many way, but would appreciate any advice on the best approach. The server is a EC2 instance, accessed through a load balancer.
If you're using express, this middleware module makes it easy to enforce https: https://www.npmjs.com/package/express-force-ssl
If you're using a reverse proxy in front of your app (ELB, nginx, etc), you'll need to set the trust proxy setting.
Here's a sample without the above module:
// Forward all requests to HTTPS.
// enable reverse proxy support in Express. This causes the
// the "X-Forwarded-Proto" header field to be trusted so its
// value can be used to determine the protocol. See
// http://expressjs.com/api#app-settings for more details.
app.enable('trust proxy');
// Add a handler to inspect the req.secure flag (see
// http://expressjs.com/api#req.secure). This allows us
// to know whether the request was via http or https.
app.use((req, res, next) => {
if (req.secure) {
// request was via https, so do no special handling
next();
} else {
// request was via http, so redirect to https
console.log('Redirecting to https');
res.redirect('https://' + req.headers.host + req.url);
}
});
Complete sample app.js
var express = require('express');
var app = express();
// Forward all requests to HTTPS.
// enable reverse proxy support in Express. This causes the
// the "X-Forwarded-Proto" header field to be trusted so its
// value can be used to determine the protocol. See
// http://expressjs.com/api#app-settings for more details.
app.enable('trust proxy');
// Add a handler to inspect the req.secure flag (see
// http://expressjs.com/api#req.secure). This allows us
// to know whether the request was via http or https.
app.use((req, res, next) => {
if (req.secure) {
// request was via https, so do no special handling
next();
} else {
// request was via http, so redirect to https
console.log('Redirecting to https');
res.redirect('https://' + req.headers.host + req.url);
}
});
// Respond to any GET requests with our message
app.get('*', (req, res) => {
res.send('This is only served over https');
});
// Listen on the assigned port
var port = process.env.PORT || 3001;
app.listen(port);
console.log('Hello started on port ' + port);
Redirect only GET requests, respond with error for non-GET requests
app.all('*', (req, res, next) => {
if (req.secure) {
next();
} else if (req.method === 'GET') {
res.redirect(`https://${req.headers.host}${req.url}`);
} else {
res.status(401).send('Secure channel required');
}
});
Related
I'm running a nodejs webapp on Heroku and I want to redirect all users that access via http to https with the corresponding URL.
I have it mostly working however the user is redirected to the home page if no subdomain is specified. Any idea what is going on here?
The node rerouting middleware:
app.enable('trust proxy');
app.use((req, res, next) => {
if (req.get('X-Forwarded-Proto') !== 'https') {
res.redirect(`https://${req.headers.host + req.url}`);
} else {
next();
}
});
Works:
http://www.example.com/page redirects to https://www.example.com/page
Fails:
http://example.com/page redirects to https://www.example.com
Because req.url is an inherited property from Node.JS's http module and does not necessarily contain the original URL. Express.JS documentation also states it, https://expressjs.com/en/api.html#req.originalUrl
If you want to retain the original url you should use the correct property, which is originalUrl.
app.enable('trust proxy');
app.use((req, res, next) => {
if (req.get('X-Forwarded-Proto') !== 'https') {
res.redirect(`https://${req.headers.host + req.originalUrl}`);
} else {
next();
}
});
Using This tutorial I was able to set up NodeJS with SSL keys. It creates two http servers (one secure, one not).
var app = express();
var sslOptions = {}; // SSL key stuff in here
var https = require('https');
https.createServer(sslOptions, app).listen(443);
var http = require('http');
http.createServer(app).listen(80);
Then it checks any incoming request -- if it's not secure, it redirects it to the secure URL
app.use(function(req, res, next) {
console.log('request... ' + req.url); // I don't see this
if (req.secure) {
console.log('secure ' + req.url);
next();
} else {
console.log('redirect... https://' + req.headers.host + req.url);
res.redirect('https://' + req.headers.host + req.url);
}
});
I see some requests come in, such as favicon.ico, when I start with a fresh browser. But the actual request to www.mydomain.com appears to bypass this all together (as do the requests I'm making to endpoints). What's worse is I had this working at one point. Not sure what I did to break it. Anywho, the result is http://www.example.com trying to hit an endpoint at https://www.example.com/endpoint and it fails (not to mention not loading the secure version of the page).
What I am trying to do:
Proxy a java api that runs on https://127.0.0.1:443/api/ along side my UI that runs on non-SSL http://127.0.0.1:1337/ in order to circumnavigate some CORS issues.
My attempt:
Proxy the api at the SSL port 443 to my non-SSL development port of 1338.
proxy my UI to 1337
Proxy 1137 to :8080/index.html and proxy 1338 to :8080/api/
Access my app from localhost:8080
My problem:
The UI comes in just fine... but I can not hit the API at :8080/api/httpSession/init
Yes, I can still hit the API at https://localhost/api/httpSession/init
api.js - Renders index.html at :1337
var app = express();
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
});
var options = {
changeOrigin: true,
target: {
https: true
}
};
httpProxy.createServer(443, '127.0.0.1', options).listen(1338);
start.js - Proxies 1337 and 1338 into 8080
// First I start my two servers
uiServer.start(); // renders index.html at 1337
apiServer.start(); //
// I attempt to patch them back into one single non-SSL port.
app
.use('/', proxy({target: 'http://localhost:1337/'}))
.all('/api/*', proxy({target: 'http://localhost:1338/'}))
.listen(8080, function () {
console.log('PROXY SERVER listening at http://localhost:%s', 8080);
});
What you're looking for is request piping. Try this example:
// Make sure request is in your package.json
// if not, npm install --save request
var request = require('request');
// Intercept all routes to /api/...
app.all('/api/*', function (req, res) {
// Get the original url, it's a fully qualified path
var apiPath = req.originalUrl;
// Form the proxied URL to your java API
var url = 'https://127.0.0.1' + apiPath;
// Fire off the request, and pipe the response
// to the res handler
request.get(url).pipe(res);
});
Make sure to add some error handling if the api can't be reached, such as this SO solution.
For the proxy issue, my guess is that it is keeping the /api/* in the url and that's not present on the router in your API service. You could try adding /api to the router in the API service since it's going to keep the url string the same when it sends it. Otherwise, you likely need to proxy and rewrite the url so that the API will match the request to a route.
On another note, what about just installing the cors module and using in the app? I do something similar and it's working well without all the proxy items. https://www.npmjs.com/package/cors
Im trying to redirect HTTP requests to my site to HTTPS. Its been extraordinarily hard. My code is:
var express = require('express');
var app = express();
app.use(function(req, res, next) {
console.log('req.protocol is ', req.protocol);
console.log('req.secure is ', req.secure);
if (req.url !== '/health' && !req.secure) {
console.log('redirecting .........');
return res.redirect('https://www.example.net/catch');
}
next();
});
app.get('/catch', function(req, res) {
res.send('Hello World!');
});
app.get('/', function(req, res) {
res.send('Hello World!');
});
app.get('/health', function(req, res) {
res.send('Hello World!');
});
app.listen(8080, function() {
console.log('Example app listening on port 8080!');
});
The load Balancer health check goes to '/health'. Every other request that isnt a health check, and is HTTP (rather than HTTPS) should be caught and redirected to https. However, I end up in an infinite loop as req.protocol always returns 'http' for http or https requests. req.secure therefore is false every time and I end up in a loop. Any ideas why this is?
It sounds like you installed the SSL certificate on your Elastic Load Balancer, so that's where SSL Termination is happening. So your load balancer is doing the SSL termination and always communicating with your server via HTTP. This means you have to check the 'x-forwarded-proto' header to determine if the original request is over HTTPS.
There are several other ways to configure SSL on AWS, including termination on the web server, but SSL termination on the ELB is the generally preferred method on AWS. You just have to be aware that in this configuration the request between the ELB and the web server isn't actually over SSL so you have to check the header accordingly.
Looks like req.protocol will always be 'http' on Amazon Web Services. Now im using:
req.get('x-forwarded-proto')
I'm trying to use a combination of 2 Express servers to redirect users from http to https and also to redirect requests with www to non www, because cert requires non www. First problem, user visits https://www.example.com they get ERR_CONNECTION_REFUSED. Second problem, user visits http://www.example.com there is a DNS error. Users can only successfully visit http://example.com (and get redirected) and https://example.com. I have tried a few solutions but a lot of the examples for Express seem outdated, what needs to change to cover the above two use cases?
// Create Express Server
server = express();
httpServer = express();
// Redirect http to https
httpServer.set('port', 80);
httpServer.get("*", function (req, res, next) {
res.redirect("https://" + req.headers.host + "/" + req.path);
});
http.createServer(httpServer).listen(80);
server.set('trust proxy', true);
// Add server static middleware
server.use( st(options.prod.st) );
server.use(function (req, res, next) {
var str = "www.";
if (req.headers.host.indexOf(str) === 0) {
res.redirect(301, req.protocol + "://" + req.headers.host.slice(str.length) + req.originalUrl);
} else {
next();
}
});
// Fallback to /index.html
server.use(fallback(prodRoot));
// Start Server
https.createServer(creds, server).listen(options.prod.port);
After adding a NS record for www, most issues were resolved. Thanks #Fella for the suggestions. Corrected code is below. I ended up also having to fix an issue if user lands on https://www.example.com I needed to add a CORS policy to prevent mixed content warnings on some XMLHttpRequests in the web app. I also remove the redirect from www to non-www, it just isn't necessary after fixing NS records.
// Create Connect Server
server = express();
httpServer = express();
// Redirect http to https
httpServer.get('*', function (req, res, next) {
res.redirect(301, 'https://' + req.headers.host + req.path);
});
http.createServer(httpServer).listen(80);
// Add serve static middleware
server.use( st(options.prod.st) );
// Implement CORS policy
server.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
// Fallback to /index.html
server.use(fallback(prodRoot));
// Start Server
https.createServer(creds, server).listen(443);