Automatic HTTPS connection/redirect with node.js/express
I've been trying to get HTTPS set up with a node.js project I'm working on. I've essentially followed the node.js documentation for this example:
// curl -k https://localhost:8000/
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
};
https.createServer(options, function (req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
Now, when I do
curl -k https://localhost:8000/
I get
hello world
as expected. But if I do
curl -k http://localhost:8000/
I get
curl: (52) Empty reply from server
In retrospect this seems obvious that it would wor开发者_如何学编程k this way, but at the same time, people who eventually visit my project aren't going to type in https://yadayada, and I want all traffic to be https from the moment they hit the site.
How can I get node (and Express as that is the framework I'm using) to hand off all incoming traffic to https, regardless of whether or not it was specified? I haven't been able to find any documentation that has addressed this. Or is it just assumed that in a production environment, node has something that sits in front of it (e.g. nginx) that handles this kind of redirection?
This is my first foray into web development, so please forgive my ignorance if this is something obvious.
Ryan, thanks for pointing me in the right direction. I fleshed out your answer (2nd paragraph) a little bit with some code and it works. In this scenario these code snippets are put in my express app:
// set up plain http server
var http = express();
// set up a route to redirect http to https
http.get('*', function(req, res) {
res.redirect('https://' + req.headers.host + req.url);
// Or, if you don't want to automatically detect the domain name from the request header, you can hard code it:
// res.redirect('https://example.com' + req.url);
})
// have it listen on 8080
http.listen(8080);
The https express server listens ATM on 3000. I set up these iptables rules so that node doesn't have to run as root:
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 3000
All together, this works exactly as I wanted it to.
To prevent theft of cookies over HTTP, see this answer (from the comments) or use this code:
const session = require('cookie-session');
app.use(
session({
secret: "some secret",
httpOnly: true, // Don't let browser javascript access cookies.
secure: true, // Only use cookies over https.
})
);
Thanks to this guy: https://www.tonyerwin.com/2014/09/redirecting-http-to-https-with-nodejs.html
If secure, requests via https, otherwise redirects to https
app.enable('trust proxy')
app.use((req, res, next) => {
req.secure ? next() : res.redirect('https://' + req.headers.host + req.url)
})
If you follow conventional ports since HTTP tries port 80 by default and HTTPS tries port 443 by default you can simply have two server's on the same machine: Here's the code:
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('./key.pem'),
cert: fs.readFileSync('./cert.pem')
};
https.createServer(options, function (req, res) {
res.end('secure!');
}).listen(443);
// Redirect from http port 80 to https
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
res.end();
}).listen(80);
Test with https:
$ curl https://127.0.0.1 -k
secure!
With http:
$ curl http://127.0.0.1 -i
HTTP/1.1 301 Moved Permanently
Location: https://127.0.0.1/
Date: Sun, 01 Jun 2014 06:15:16 GMT
Connection: keep-alive
Transfer-Encoding: chunked
More details : Nodejs HTTP and HTTPS over same port
With Nginx you can take advantage of the "x-forwarded-proto" header:
function ensureSec(req, res, next){
if (req.headers["x-forwarded-proto"] === "https"){
return next();
}
res.redirect("https://" + req.headers.host + req.url);
}
As of 0.4.12 we have no real clean way of listening for HTTP & HTTPS on the same port using Node's HTTP/HTTPS servers.
Some people have solved this issue by having having Node's HTTPS server (this works with Express.js as well) listen to 443 (or some other port) and also have a small http server bind to 80 and redirect users to the secure port.
If you absolutely have to be able to handle both protocols on a single port then you need to put nginx, lighttpd, apache, or some other web server on that port and have act as a reverse proxy for Node.
I use the solution proposed by Basarat but I also need to overwrite the port because I used to have 2 different ports for HTTP and HTTPS protocols.
res.writeHead(301, { "Location": "https://" + req.headers['host'].replace(http_port,https_port) + req.url });
I prefer also to use not standard port so to start nodejs without root privileges. I like 8080 and 8443 because I came from lots of years of programming on tomcat.
My complete file become
var fs = require('fs');
var http = require('http');
var http_port = process.env.PORT || 8080;
var app = require('express')();
// HTTPS definitions
var https = require('https');
var https_port = process.env.PORT_HTTPS || 8443;
var options = {
key : fs.readFileSync('server.key'),
cert : fs.readFileSync('server.crt')
};
app.get('/', function (req, res) {
res.send('Hello World!');
});
https.createServer(options, app).listen(https_port, function () {
console.log('Magic happens on port ' + https_port);
});
// Redirect from http port to https
http.createServer(function (req, res) {
res.writeHead(301, { "Location": "https://" + req.headers['host'].replace(http_port,https_port) + req.url });
console.log("http request, will go to >> ");
console.log("https://" + req.headers['host'].replace(http_port,https_port) + req.url );
res.end();
}).listen(http_port);
Then I use iptable for forwording 80 and 443 traffic on my HTTP and HTTPS ports.
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 8443
You can use the express-force-https module:
npm install --save express-force-https
var express = require('express');
var secure = require('express-force-https');
var app = express();
app.use(secure);
I find req.protocol works when I am using express (have not tested without but I suspect it works). using current node 0.10.22 with express 3.4.3
app.use(function(req,res,next) {
if (!/https/.test(req.protocol)){
res.redirect("https://" + req.headers.host + req.url);
} else {
return next();
}
});
This answer needs to be updated to work with Express 4.0. Here is how I got the separate http server to work:
var express = require('express');
var http = require('http');
var https = require('https');
// Primary https app
var app = express()
var port = process.env.PORT || 3000;
app.set('env', 'development');
app.set('port', port);
var router = express.Router();
app.use('/', router);
// ... other routes here
var certOpts = {
key: '/path/to/key.pem',
cert: '/path/to/cert.pem'
};
var server = https.createServer(certOpts, app);
server.listen(port, function(){
console.log('Express server listening to port '+port);
});
// Secondary http app
var httpApp = express();
var httpRouter = express.Router();
httpApp.use('*', httpRouter);
httpRouter.get('*', function(req, res){
var host = req.get('Host');
// replace the port in the host
host = host.replace(/:\d+$/, ":"+app.get('port'));
// determine the redirect destination
var destination = ['https://', host, req.url].join('');
return res.redirect(destination);
});
var httpServer = http.createServer(httpApp);
httpServer.listen(8080);
If your app is behind a trusted proxy (e.g. an AWS ELB or a correctly configured nginx), this code should work:
app.enable('trust proxy');
app.use(function(req, res, next) {
if (req.secure){
return next();
}
res.redirect("https://" + req.headers.host + req.url);
});
Notes:
- This assumes that you're hosting your site on 80 and 443, if not, you'll need to change the port when you redirect
- This also assumes that you're terminating the SSL on the proxy. If you're doing SSL end to end use the answer from @basarat above. End to end SSL is the better solution.
- app.enable('trust proxy') allows express to check the X-Forwarded-Proto header
you can use "net" module to listening for HTTP & HTTPS on the same port
var https = require('https');
var http = require('http');
var fs = require('fs');
var net=require('net');
var handle=net.createServer().listen(8000)
var options = {
key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
};
https.createServer(options, function (req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(handle);
http.createServer(function(req,res){
res.writeHead(200);
res.end("hello world\n");
}).listen(handle)
Most answers here suggest to use the req.headers.host header.
The Host header is required by HTTP 1.1, but it is actually optional since the header might not be actually sent by a HTTP client, and node/express will accept this request.
You might ask: which HTTP client (e.g: browser) can send a request missing that header? The HTTP protocol is very trivial. You can craft a HTTP request in few lines of code, to not send a host header, and if each time you receive a malformed request you throw an exception, and depending on how you handle such exceptions, this can take your server down.
So always validate all input. This is not paranoia, I have received requests lacking the host header in my service.
Also, never treat URLs as strings. Use the node url module to modify specific parts of a string. Treating URLs as strings can be exploited in many many many ways. Don't do it.
var express = require('express');
var app = express();
app.get('*',function (req, res) {
res.redirect('https://<domain>' + req.url);
});
app.listen(80);
This is what we use and it works great!
This worked for me:
app.use(function(req,res,next) {
if(req.headers["x-forwarded-proto"] == "http") {
res.redirect("https://[your url goes here]" + req.url, next);
} else {
return next();
}
});
This script while loading the page saves the URL page and checks if the address is https or http. If it is http, the script automatically redirects you to the same https page
(function(){
var link = window.location.href;
if(link[4] != "s"){
var clink = "";
for (let i = 4; i < link.length; i++) {
clink += link[i];
}
window.location.href = "https" + clink;
}
})();
You can instantiate 2 Node.js servers - one for HTTP and HTTPS
You can also define a setup function that both servers will execute, so that you don't have to write much duplicated code.
Here's the way I did it: (using restify.js, but should work for express.js, or node itself too)
http://qugstart.com/blog/node-js/node-js-restify-server-with-both-http-and-https/
Updated code for jake's answer. Run this alongside your https server.
// set up plain http server
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app);
// set up a route to redirect http to https
app.get('*', function(req, res) {
res.redirect('https://' + req.headers.host + req.url);
})
// have it listen on 80
server.listen(80);
This works with express for me:
app.get("*",(req,res,next) => {
if (req.headers["x-forwarded-proto"]) {
res.redirect("https://" + req.headers.host + req.url)
}
if (!res.headersSent) {
next()
}
})
Put this before all HTTP handlers.
This worked for me:
/* Headers */
require('./security/Headers/HeadersOptions').Headers(app);
/* Server */
const ssl = {
key: fs.readFileSync('security/ssl/cert.key'),
cert: fs.readFileSync('security/ssl/cert.pem')
};
//https server
https.createServer(ssl, app).listen(443, '192.168.1.2' && 443, '127.0.0.1');
//http server
app.listen(80, '192.168.1.2' && 80, '127.0.0.1');
app.use(function(req, res, next) {
if(req.secure){
next();
}else{
res.redirect('https://' + req.headers.host + req.url);
}
});
Recommend add the headers before redirect to https
Now, when you do:
curl http://127.0.0.1 --include
You get:
HTTP/1.1 302 Found
//
Location: https://127.0.0.1/
Vary: Accept
Content-Type: text/plain; charset=utf-8
Content-Length: 40
Date: Thu, 04 Jul 2019 09:57:34 GMT
Connection: keep-alive
Found. Redirecting to https://127.0.0.1/
I use express 4.17.1
The idea is to check if the incoming request is made with https, if so simply don't redirect it again to https but continue as usual. Else, if it is http, redirect it with appending https.
app.use (function (req, res, next) {
if (req.secure) {
next();
} else {
res.redirect('https://' + req.headers.host + req.url);
}
});
From the long years of reseaching for a perfect redirection from http to https, I've found the perfect solution here.
const http = require("http");
const https = require("https");
const { parse } = require("url");
const next = require("next");
const fs = require("fs");
const ports = {
http: 3000,
https: 3001
}
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
const httpsOptions = {
key: fs.readFileSync("resources/certificates/localhost-key.pem"),
cert: fs.readFileSync("resources/certificates/localhost.pem")
};
// Automatic HTTPS connection/redirect with node.js/express
// source: https://stackoverflow.com/questions/7450940/automatic-https-
connection-redirect-with-node-js-express
app.prepare().then(() => {
// Redirect from http port to https
http.createServer(function (req, res) {
res.writeHead(301, { "Location": "https://" + req.headers['host'].replace(ports.http, ports.https) + req.url });
console.log("http request, will go to >> ");
console.log("https://" + req.headers['host'].replace(ports.http, ports.https) + req.url);
res.end();
}).listen(ports.http, (err) => {
if (err) throw err;
console.log("ready - started server on url: http://localhost:" + ports.http);
});
https.createServer(httpsOptions, (req, res) => {
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
}).listen(ports.https, (err) => {
if (err) throw err;
console.log("ready - started server on url: https://localhost:" + ports.https);
});
});
My case i must change also the port and listen both ports:
appr.get("/", (req, res) => {
res.redirect('https://' + req.headers['host'].replace(PORT, PORTS) + req.url);
});
if your node application install on IIS you can do like this in web.config
<configuration>
<system.webServer>
<!-- indicates that the hello.js file is a node.js application
to be handled by the iisnode module -->
<handlers>
<add name="iisnode" path="src/index.js" verb="*" modules="iisnode" />
</handlers>
<!-- use URL rewriting to redirect the entire branch of the URL namespace
to hello.js node.js application; for example, the following URLs will
all be handled by hello.js:
http://localhost/node/express/myapp/foo
http://localhost/node/express/myapp/bar
-->
<rewrite>
<rules>
<rule name="HTTPS force" enabled="true" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="^OFF$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent" />
</rule>
<rule name="sendToNode">
<match url="/*" />
<action type="Rewrite" url="src/index.js" />
</rule>
</rules>
</rewrite>
<security>
<requestFiltering>
<hiddenSegments>
<add segment="node_modules" />
</hiddenSegments>
</requestFiltering>
</security>
</system.webServer>
</configuration>
精彩评论