Hello,
I'm using nodejitsu to host my app and I need to redirect all http requests to https at the server level.
https://www.nodejitsu.com/documentation/a-quickstart/faq/#how-do-i-force-my-clients-to-use-https-with-my-application
I'm not quire sure how to do this using hapi. I believe I should use the "onReqest' extension, but that only provides setUrl and setMethod methods. I need to force a redirect at this stage.
Can you please advise the best way to achieve this.
Thanks,
Jozz
Create two servers and redirect all requests from one to the other:
var Hapi = require('hapi');
var http = new Hapi.Server(80);
var https = new Hapi.Server(443, { tls: {} });
var redirect = function () {
this.reply.redirect('https://your.site/' + this.params.path);
});
http.route({ method: '*', path: '/{path*}', handler: redirect });
Note that this is using current 1.0 rc in master. In previous versions (0.16.0 from npm) use:
this.reply.redirect('https://your.site/' + this.params.path).send();
Thanks hueniverse for the prompt reply. Makes sense. Cheers Jozz
How would I do that on heroku or openshift which assign just one port to your app and take care of the whole ssl business themselves? This is my solution so far but it seems rather hacky:
if ('production' === process.env.NODE_ENV || 'staging' === process.env.NODE_ENV) {
this.server.ext('onRequest', function (request, next) {
if (request.headers['x-forwarded-proto'] !== 'https') {
request.originalPath = request.path;
request.setUrl('/redirect');
}
next();
});
this.server.route([{
method: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
path: '/redirect',
handler: function (request, reply) {
var host = request.headers.host;
reply().redirect('https://' + host + request.originalPath);
}
}]);
}
@chmanie you can just redirect right from onRequest.
You mean like this?
// redirect all http request to secure route
if ('production' === process.env.NODE_ENV || 'staging' === process.env.NODE_ENV) {
server.ext('onRequest', function (request, reply) {
if (request.headers['x-forwarded-proto'] !== 'https') {
return reply('Forwarding to secure route')
.redirect('https://' + request.headers.host + request.path);
}
reply();
});
}
Yep.
Thank you, this seems to work except I run into this issue when trying to access the non protected route: https://github.com/spumko/yar/issues/34
@chmanie I have the same issue (single port), where exactly are you putting that code to handle the redirect?
You basically can put it wherever you have access to the server object (you're not in express js :smile: ). But keep in mind that you have to use a yar version with the above issue resolved.
@chmanie I did
server.ext('onRequest',function(request,next){
console.log('test');
next()
});
When I enter https://localhost:8000 I see 'test' in the console. When I enter http://localhost:8000, I don't see anything in the console and in the browser I just see: 'waiting for localhost'.
For anyone Googling this, I popped my redirect into a plugin:
@carlo-m Did you ever find a solution to redirecting HTTP to HTTPS using a single port? I'm experiencing the exact same issue you described.
@bendrucker @chmanie I'm using code almost exactly like the examples on this page and the plugin - It works fine for a HTTPS request but the browser just hangs for a HTTP request - any ideas what I'm doing wrong? If you set up TLS on the connection maybe it ignores HTTP requests?
server.connection({
port: 3000,
host: localhost,
tls: {
key: fs.readFileSync(__dirname + '/tls/server.key', 'utf8'),
cert: fs.readFileSync(__dirname + '/tls/server.crt', 'utf8')
}
});
server.ext('onRequest', function (request, reply) {
if (request.headers['x-forwarded-proto'] === 'http') {
console.log('redirect to https');
return reply().redirect('https://' + request.headers.host + request.url.path).code(301);
}
console.log('NO redirect');
return reply.continue();
});
https://localhost:3000 // works fine
http://localhost:3000 // browser hangs, waiting for localhost
@kristiankeane which version of hapi are you using?
@chmanie I think @kristiankeane solved it with help from the gitter
I made a rather elaborate example, to connect the dots, useful for noobs ;-)
You can make an entry in manifest.js, ie.:
const tls = {};
if (process.env.NODE_ENV === 'production') {
tls.key = Fs.readFileSync('/letsencrypt/etc/live/example.com/privkey.pem','utf8');
tls.cert = Fs.readFileSync('/letsencrypt/etc/live/example.com/cert.pem','utf8');
}
...
connections: [{
host: {
$filter: 'env',
production: '0.0.0.0',
$default: 'localhost'
},
port: 3000,
tls: {
$filter: 'env',
production: {
key: tls.key,
cert: tls.cert
},
$default: undefined
},
labels: ['api']
}, {
port: 3001,
labels: ['https-redirect']
}]
Register the plugin(https-redirect) in manifest.js:
plugin: {
register: './plugins/https-redirect',
options: {
select: ['https-redirect'],
method: '*',
path: '/{path*}',
uri: {
$filter: 'env',
production: 'https://production.example.com',
$default: 'http://localhost:3000'
}
}
}
Write the plugin(./plugins/https-redirect.js) :
exports.register = function (servers, options, next) {
const server = servers.select(options.select);
/* Assumption: you already made sure your regular routes
* are not written to 3001-server.
*/
server.route({
method: options.method,
path: options.path,
handler: (request,reply) => {
reply.redirect(options.uri + request.path);
}
});
next();
};
exports.register.attributes = {
name: 'https-redirect',
version: '0.0.1'
};
Docker run:
// 'letsencrypt' is a volume with pre-made certificates that is mounted in container
docker run -it -d -p 443:3000 -p 80:3001 -v letsencrypt:/letsencrypt app-image
I've created a simple hapi plugin for easy https redirections. You can find it here:
https://github.com/captainjackrana/hapi-gate
Most helpful comment
Create two servers and redirect all requests from one to the other:
Note that this is using current 1.0 rc in master. In previous versions (0.16.0 from npm) use: