├── examples └── example.js ├── test ├── keys │ ├── localhost.csr │ ├── localhost.crt │ └── localhost.key ├── https.js ├── server │ └── index.js ├── variable-overide.js ├── https-disable-301s.js ├── x-forwarded-proto-trusted.js ├── x-forwarded-proto.js └── http.js ├── LICENSE ├── package.json ├── index.js ├── .gitignore └── README.md /examples/example.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , forceSSL = require('./../index') 3 | , fs = require('fs') 4 | , http = require('http') 5 | , https = require('https') 6 | ; 7 | 8 | var ssl_options = { 9 | key: fs.readFileSync('./test/keys/localhost.key'), 10 | cert: fs.readFileSync('./test/keys/localhost.crt'), 11 | ca: fs.readFileSync('./test/keys/localhost.crt') 12 | }; 13 | 14 | var app = express(); 15 | var server = http.createServer(app); 16 | var secureServer = https.createServer(ssl_options, app); 17 | 18 | app.get('/', function(req, res){ 19 | res.json({msg: 'accessible by http'}); 20 | }); 21 | app.get('/ssl', forceSSL, function(req, res){ 22 | res.json({msg: 'only https'}); 23 | }); 24 | 25 | app.get('/ssl/deep/route/:id', forceSSL, function(req, res){ 26 | var host = req.headers.host.split(':'); 27 | var port = host.length > 1 ? host[1] : 'default port'; 28 | res.json({msg: 'only https, port: ' + port, id: req.param('id')}); 29 | }); 30 | 31 | app.set('httpsPort', 8443); 32 | 33 | secureServer.listen(8443); 34 | server.listen(8080); -------------------------------------------------------------------------------- /test/keys/localhost.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICwzCCAasCAQAwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEx 3 | EjAQBgNVBAcTCUhvbGx5d29vZDElMCMGA1UEChMcZXhwcmVzcy1iYXR0bGVuZXQt 4 | b2F1dGgtdGVzdDELMAkGA1UECxMCSVQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIw 5 | DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALWbWCg0evxLwD5Z1lmV9GJQkcBJ 6 | kCY3yZNU2fvxLcK+1PVo0a0aHjXPaBlaU5y3xgazPtU7T6H+DKgW5tKVPPcZsiIj 7 | e8vwH/mE5U3IIzmaxPJZPvpErCHSx9Ite4J7mrt2WcIAy95wjiu1//KkpHxpI11n 8 | oTh87+6QqxV5YZH2L0plHp5IzNJHdb8crvOEsV01g3ymjthQY9OXQHZm9+vHG3Ej 9 | VzHB41Bh3Mk9nq5cCUef10yHbTW8jusyf58CBO4y+ofYs7dlQjPpzmddpFYoIkjW 10 | spZWy+w/6+nPVTkyNZr8jnAhNbjSdbZezpuq8qoCHoCK6XHPecrtJH9ToyECAwEA 11 | AaAAMA0GCSqGSIb3DQEBBQUAA4IBAQBmUj7lzaQpYuRHGRlwmRs52rLZzsNmqcBQ 12 | 7/E7QrMKeRYHOuhOJPTvbNbYdDuR9zHenTxJvp2C3Ufw7cl0XoH0swUSu1nix+E3 13 | Wx8TnsDzSkE3dwEgdT4mXD77Ei9FvVOPGZdJkiPvUAeICprI+RhAwMEBpMKGEr57 14 | 6stYK+tyQ/FN7WKsRN+tUq7Kjs4+645x45lIwiGqkfDhjjA1GcYkRd9J+Eo+JtNo 15 | NRcLFd+KRatCN0RL5HqBPHBSYd9/WtPJbKujNHU+a3KEoxKPATg8E9Lgs69s6TZP 16 | io5ZcfppFGy/67JtN5LTwH8h0/kQsNV4pJV2NtzhrKx4NfnGUavK 17 | -----END CERTIFICATE REQUEST----- 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2013 Jeremy Battle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the 'Software'), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jeremy Battle <@complexcarb> (http://jeremybattle.com)", 3 | "name": "express-force-ssl", 4 | "description": "Force SSL on particular/all pages in Express", 5 | "version": "0.3.2", 6 | "scripts": { 7 | "test": "mocha test" 8 | }, 9 | "homepage": "http://github.com/battlejj/express-force-ssl", 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/battlejj/express-force-ssl.git" 13 | }, 14 | "contributors": [ 15 | { 16 | "name": "Jeremy Battle", 17 | "email": "battlejj@gmail.com" 18 | } 19 | ], 20 | "keywords": [ 21 | "ssl", 22 | "tls", 23 | "https", 24 | "express" 25 | ], 26 | "dependencies": { 27 | "lodash.assign": "^3.2.0" 28 | }, 29 | "main": "index", 30 | "bugs": { 31 | "url": "http://github.com/battlejj/express-force-ssl/issues" 32 | }, 33 | "engines": { 34 | "node": ">=0.2.2" 35 | }, 36 | "licenses": [ 37 | { 38 | "type": "MIT", 39 | "url": "http://github.com/battlejj/express-force-ssl/raw/master/LICENSE" 40 | } 41 | ], 42 | "devDependencies": { 43 | "body-parser": "^1.9.0", 44 | "chai": "^1.9.1", 45 | "express": "^4.9.4", 46 | "mocha": "^1.21.4", 47 | "request": "^2.44.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/keys/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDeDCCAmACCQC+YKNm0V1QRTANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJV 3 | UzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJSG9sbHl3b29kMSUwIwYD 4 | VQQKExxleHByZXNzLWJhdHRsZW5ldC1vYXV0aC10ZXN0MQswCQYDVQQLEwJJVDES 5 | MBAGA1UEAxMJbG9jYWxob3N0MB4XDTE0MDgyODE3NDMyMFoXDTE3MDYxNzE3NDMy 6 | MFowfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcT 7 | CUhvbGx5d29vZDElMCMGA1UEChMcZXhwcmVzcy1iYXR0bGVuZXQtb2F1dGgtdGVz 8 | dDELMAkGA1UECxMCSVQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcN 9 | AQEBBQADggEPADCCAQoCggEBALWbWCg0evxLwD5Z1lmV9GJQkcBJkCY3yZNU2fvx 10 | LcK+1PVo0a0aHjXPaBlaU5y3xgazPtU7T6H+DKgW5tKVPPcZsiIje8vwH/mE5U3I 11 | IzmaxPJZPvpErCHSx9Ite4J7mrt2WcIAy95wjiu1//KkpHxpI11noTh87+6QqxV5 12 | YZH2L0plHp5IzNJHdb8crvOEsV01g3ymjthQY9OXQHZm9+vHG3EjVzHB41Bh3Mk9 13 | nq5cCUef10yHbTW8jusyf58CBO4y+ofYs7dlQjPpzmddpFYoIkjWspZWy+w/6+nP 14 | VTkyNZr8jnAhNbjSdbZezpuq8qoCHoCK6XHPecrtJH9ToyECAwEAATANBgkqhkiG 15 | 9w0BAQUFAAOCAQEAE9+sbbiwLCPRwG24B4KB3eJ+IblNNsBJfvCuYneuyi1pWwCU 16 | 6BBotEWENFlIoUXO/yTR/uDvMfcvs5YmarIu3Suj5+qf0rL0b42317uGFvYBsVIA 17 | 0uG8/rFP8HyUCfKLZL2NvLkG1EaywlCW2MnfD6U6haTCUaAkaIpy6hHOU1P+dMDI 18 | OuNyG6wdeujlx2WWyag7uqr5YeKpVEpmEZUa2Dr2O0aEIU3OByuxYY8/1fwbWkbC 19 | GuOP88J/t6Ahs1DcqYsX+aE8OvMnEL6hhd1UqOUC2jh6DkxIxsQqakSRYb8PcSdL 20 | 3+5RREr8os2Futi06PR5+r67Hva/k+oaysAN+g== 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /test/keys/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAtZtYKDR6/EvAPlnWWZX0YlCRwEmQJjfJk1TZ+/Etwr7U9WjR 3 | rRoeNc9oGVpTnLfGBrM+1TtPof4MqBbm0pU89xmyIiN7y/Af+YTlTcgjOZrE8lk+ 4 | +kSsIdLH0i17gnuau3ZZwgDL3nCOK7X/8qSkfGkjXWehOHzv7pCrFXlhkfYvSmUe 5 | nkjM0kd1vxyu84SxXTWDfKaO2FBj05dAdmb368cbcSNXMcHjUGHcyT2erlwJR5/X 6 | TIdtNbyO6zJ/nwIE7jL6h9izt2VCM+nOZ12kVigiSNayllbL7D/r6c9VOTI1mvyO 7 | cCE1uNJ1tl7Om6ryqgIegIrpcc95yu0kf1OjIQIDAQABAoIBAAxqx7dQB0yy3T0m 8 | JVrQvvnt6llMble+nsC9H35zeh6Dr8nr1dJRI9moCcUaAPeJNTgGD3jC6mn4FeN0 9 | VWn2nEmE70IYTQGftH/6DzenRIlOxMKRSZYRFffmEpWTWIuOagEBUZfLOCVIauAg 10 | PJTZnwmGos1jJYnYOQuFxrzcJMi2//5o4lzy9fCyGnVX1S1K2aVAsTKziZxj1mEI 11 | 6+QQJo+57tMGHrOZl6pJHWsfjd/DGeLtAA9PRstkzWUG3e+cVTlGH+Vr9f6foq44 12 | TRWDKUcCzy8fKXhmguBiJkRmY6CjiWKbx0EZ5BV5js1jXvAwzcqrCBHxlZedZggI 13 | EohelYUCgYEA2DUVjNBYoxBk/3C2ZLXi954ZzN0hEa/Pv8DLHn57448aoOVb/+H0 14 | qzgfhibc/y6+pWearW7EERIfp3ZprcnRYkNGC9hc4aGP1ae4AOZfm0TkdpAYiTNc 15 | 3vV+PtI3iv6/qZPNktqk55jo6WMmw3MUfy66TwUYPUzZpXm6hdBUtOcCgYEA1wgB 16 | qDV/G+T1w2gIy6IfPnQw/0UoHtcuRcJIrjlF0tc/KEf36tZwxHhr8i+ayBmU9HhH 17 | Q46eZAq6KrVE9ysnyirDRllW8qxV5Go0A3ICnirL6jnWSzuOu9aIn4VcB8F+Xx2R 18 | th7gCzRUBdgJWYJL9FcR86WhM+5my7kciRAq3rcCgYA1Zq8i75bk97iqavFx4Ibl 19 | uBQRSJDRaIY8i2bf6ke5RfBCy0O06N9gpuUKYnD1SltmSTeoHJKq0Lomx5WEijOA 20 | PLOBW3hddmUrVViaSExW8mYnbqHQyXHn0+TRqWR0nUVDojEFU6GlXlwwwP+jCLqI 21 | S0dTGyQIiAG94FoUkQdLAwKBgQC6N4nP1PxN+NtIrSioyK6MFG12M7rJ8ol1CgqN 22 | LrYkIBnm1WSCr9CapLq+0rEVRuozSJJWlAThGFUetTqTXoEn2B6iJq5gnBQKKlr+ 23 | /NX9iYxsPEgzgNFcJC7PDtujL9MzpdTRRi26Jkf5g5ydMnR6lojKWo6e/X9yP83R 24 | ePnXQwKBgQCiHjWzMNRbeqjjWtaeb3Wv3QkKGZkwOgrOlqODZcZ4kNalDeh2Q4Ho 25 | cWUsbG4ko8J6yWnnhzRxG2G5Q/W6rpzZWCNsazKaz5LI0svYjWFOyIUvNjO5giFx 26 | udNcjnqrwql/F8xZK7YoiMuM6ltU03NY1lpUh/X4Gd3ThXnhr8DLRw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/https.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai') 2 | , expect = chai.expect 3 | , request = require('request') 4 | , server 5 | , secureBaseurl 6 | , SSLRequiredErrorText 7 | ; 8 | 9 | before(function () { 10 | server = require('./server')({ httpPort: 8086, httpsPort: 6443 }); 11 | secureBaseurl = 'https://localhost:' + server.securePort; 12 | SSLRequiredErrorText = 'SSL Required.'; 13 | }); 14 | 15 | describe('Test standard HTTPS behavior.', function() { 16 | 17 | it('Should have no redirection from SSL on non "SSL Only" endpoint.', function (done) { 18 | request.get({ 19 | url: secureBaseurl, 20 | followRedirect: false, 21 | strictSSL: false 22 | }, function (error, response, body) { 23 | //noinspection BadExpressionStatementJS 24 | expect(error).to.not.exist; 25 | expect(response.statusCode).to.equal(200); 26 | expect(body).to.equal('HTTP and HTTPS.'); 27 | done(); 28 | }); 29 | }); 30 | 31 | it('Should have no redirection from SSL on "SSL Only" endpoint.', function (done) { 32 | request.get({ 33 | url: secureBaseurl + '/ssl', 34 | followRedirect: false, 35 | strictSSL: false 36 | }, function (error, response, body) { 37 | //noinspection BadExpressionStatementJS 38 | expect(error).to.not.exist; 39 | expect(response.statusCode).to.equal(200); 40 | expect(body).to.equal('HTTPS only.'); 41 | done(); 42 | }); 43 | }); 44 | 45 | it('Should successfully POST to an "SSL Only" endpoint.', function(done){ 46 | var destination = secureBaseurl + '/sslEcho'; 47 | var postData = { key1: 'Keyboard.', key2: 'Cat.'}; 48 | request.post({ 49 | url: destination, 50 | followRedirect: false, 51 | strictSSL: false, 52 | form: postData 53 | }, function(error, response, body){ 54 | //noinspection BadExpressionStatementJS 55 | expect(error).to.not.exist; 56 | expect(response.statusCode).to.equal(200); 57 | expect(response.request.uri.href).to.equal(destination); 58 | expect(body).to.equal(JSON.stringify(postData)); 59 | done(); 60 | }); 61 | }); 62 | 63 | }); -------------------------------------------------------------------------------- /test/server/index.js: -------------------------------------------------------------------------------- 1 | var bodyParser = require('body-parser') 2 | , express = require('express') 3 | , forceSSL = require('../../index') 4 | , fs = require('fs') 5 | , http = require('http') 6 | , https = require('https') 7 | ; 8 | 9 | module.exports = function (options) { 10 | var ssl_options = { 11 | key: fs.readFileSync('./test/keys/localhost.key'), 12 | cert: fs.readFileSync('./test/keys/localhost.crt') 13 | }; 14 | 15 | options = options || {}; 16 | 17 | var httpPort = options.httpPort || 8080; 18 | var httpsPort = options.httpsPort || 8443; 19 | 20 | delete options.httpPort; 21 | 22 | var app = express(); 23 | 24 | /* 25 | Allow for testing with POSTing of data 26 | */ 27 | app.use(bodyParser.urlencoded({ extended: false })); 28 | app.use(bodyParser.json()); 29 | 30 | var server = http.createServer(app); 31 | var secureServer = https.createServer(ssl_options, app); 32 | 33 | /* 34 | Routes 35 | */ 36 | app.get('/', function (req, res) { 37 | res.send('HTTP and HTTPS.'); 38 | }); 39 | 40 | app.get('/ssl', forceSSL, function (req, res) { 41 | res.send('HTTPS only.'); 42 | }); 43 | 44 | app.get('/ssl/nested/route/:id', forceSSL, function (req, res) { 45 | var host = req.headers.host.split(':'); 46 | var port = host.length > 1 ? host[1] : 'default port'; 47 | res.send('HTTPS Only. Port: ' + port + '. Got param of ' + req.params.id + '.'); 48 | }); 49 | 50 | app.post('/echo', function (req, res) { 51 | res.json(req.body); 52 | }); 53 | 54 | app.post('/sslEcho', forceSSL, function (req, res) { 55 | res.json(req.body); 56 | }); 57 | 58 | app.get('/override', function (req, res, next) { 59 | res.locals.forceSSLOptions = { 60 | enable301Redirects: false 61 | }; 62 | next(); 63 | }, forceSSL, function (req, res) { 64 | res.json(req.body); 65 | }); 66 | 67 | //Old Usage 68 | //app.set('httpsPort', httpsPort); 69 | app.set('forceSSLOptions', options); 70 | secureServer.listen(httpsPort); 71 | server.listen(httpPort); 72 | 73 | return { 74 | secureServer: secureServer, 75 | server: server, 76 | app: app, 77 | securePort: httpsPort, 78 | port: httpPort, 79 | options: options 80 | }; 81 | }; 82 | 83 | -------------------------------------------------------------------------------- /test/variable-overide.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai') 2 | , expect = chai.expect 3 | , request = require('request') 4 | , server 5 | , baseurl 6 | , secureBaseurl 7 | , SSLRequiredErrorText = 'Custom SSL Required Message.' 8 | ; 9 | 10 | before(function () { 11 | server = require('./server')({ 12 | enable301Redirects: false, 13 | httpPort: 8091, 14 | httpsPort: 11443, 15 | sslRequiredMessage: SSLRequiredErrorText 16 | }); 17 | baseurl = 'http://localhost:' + server.port; 18 | secureBaseurl = 'https://localhost:' + server.securePort; 19 | }); 20 | 21 | describe('Test HTTPS behavior when 301 redirects are disabled.', function () { 22 | it('301 Redirect should be disabled by user setting', function (done) { 23 | 24 | var endpoint = baseurl + '/ssl'; 25 | 26 | request.get({ 27 | url: endpoint, 28 | followRedirect: false, 29 | strictSSL: false 30 | }, function (error, response, body) { 31 | //noinspection BadExpressionStatementJS 32 | expect(error).to.not.exist; 33 | expect(response.statusCode).to.equal(403); 34 | done() 35 | }); 36 | 37 | }); 38 | 39 | it('301 Redirect should be enabled by res.local setting', function (done) { 40 | 41 | var sslEndpoint = secureBaseurl + '/override'; 42 | 43 | request.get({ 44 | url: sslEndpoint, 45 | followRedirect: false, 46 | strictSSL: false 47 | }, function (error, response, body) { 48 | //noinspection BadExpressionStatementJS 49 | expect(error).to.not.exist; 50 | expect(response.statusCode).to.equal(200); 51 | done(); 52 | }); 53 | 54 | }); 55 | 56 | it('301 Redirect should be enabled by res.local setting', function (done) { 57 | 58 | var sslEndpoint = secureBaseurl + '/override'; 59 | 60 | request.get({ 61 | url: sslEndpoint, 62 | followRedirect: false, 63 | strictSSL: false 64 | }, function (error, response, body) { 65 | //noinspection BadExpressionStatementJS 66 | expect(error).to.not.exist; 67 | expect(response.statusCode).to.equal(200); 68 | done(); 69 | }); 70 | 71 | }); 72 | 73 | it('Custom error text test', function (done) { 74 | 75 | var endpoint = baseurl + '/ssl'; 76 | 77 | request.get({ 78 | url: endpoint, 79 | followRedirect: false, 80 | strictSSL: false 81 | }, function (error, response, body) { 82 | //noinspection BadExpressionStatementJS 83 | expect(error).to.not.exist; 84 | expect(response.statusCode).to.equal(403); 85 | expect(body).to.equal(SSLRequiredErrorText); 86 | done(); 87 | }); 88 | 89 | }); 90 | 91 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var parseUrl = require('url').parse; 2 | var assign = require('lodash.assign'); 3 | 4 | function isSecure (secure, xfpHeader, trustXFPHeader) { 5 | xfpHeader = xfpHeader ? xfpHeader.toString().toLowerCase() : ''; 6 | if (secure) { 7 | return true; 8 | } 9 | 10 | return trustXFPHeader && xfpHeader === 'https' 11 | } 12 | 13 | function shouldRedirect (redirectsEnabled, method) { 14 | if (!redirectsEnabled) { 15 | return false 16 | } 17 | 18 | return method === "GET"; 19 | } 20 | 21 | function checkForDeprecation (appSettings, optionsHttpsPort) { 22 | var httpsPort = appSettings.get('httpsPort'); 23 | 24 | if (appSettings.get('env') === 'development') { 25 | if (httpsPort) { 26 | console.warn('express-force-ssl deprecated: app.set("httpsPort", ' + httpsPort + '), use ' + 27 | 'app.set("forceSSLOptions", { httpsPort: ' + httpsPort + ' }) instead.'); 28 | } 29 | 30 | if (httpsPort && optionsHttpsPort) { 31 | console.warn('You have set both app.get("httpsPort") and app.get("forceSSLOptions").httpsPort ' + 32 | 'Your app will use the value in forceSSLOptions.'); 33 | } 34 | } 35 | } 36 | 37 | module.exports = function(req, res, next){ 38 | var redirect; 39 | var secure; 40 | var xfpHeader = req.get('X-Forwarded-Proto'); 41 | var localHttpsPort; 42 | var appHttpsPort = req.app.get('httpsPort'); 43 | var httpsPort; 44 | var fullUrl; 45 | var redirectUrl; 46 | 47 | var options = { 48 | trustXFPHeader: false, 49 | enable301Redirects: true, 50 | httpsPort: false, 51 | sslRequiredMessage: 'SSL Required.' 52 | }; 53 | 54 | var expressOptions = req.app.get('forceSSLOptions') || {}; 55 | var localOptions = res.locals.forceSSLOptions || {}; 56 | localHttpsPort = localOptions.httpsPort; 57 | assign(options, expressOptions, localOptions); 58 | 59 | secure = isSecure(req.secure, xfpHeader, options.trustXFPHeader); 60 | redirect = shouldRedirect(options.enable301Redirects, req.method); 61 | 62 | if (!secure) { 63 | if (redirect) { 64 | checkForDeprecation(req.app, options.httpsPort); 65 | 66 | httpsPort = localHttpsPort || options.httpsPort || appHttpsPort || 443; 67 | fullUrl = parseUrl(req.protocol + '://' + req.header('Host') + req.originalUrl); 68 | 69 | //intentionally allow coercion of https port 70 | redirectUrl = 'https://' + fullUrl.hostname + (httpsPort == 443 ? '' : (':' + httpsPort)) + req.originalUrl; 71 | 72 | res.redirect(301, redirectUrl); 73 | } else { 74 | res.status(403).send(options.sslRequiredMessage); 75 | } 76 | } else { 77 | delete res.locals.forceSSLOptions; 78 | next(); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## JetBrains 3 | ################# 4 | .idea/ 5 | 6 | ################# 7 | ## Eclipse 8 | ################# 9 | 10 | *.pydevproject 11 | .project 12 | .metadata 13 | bin/ 14 | tmp/ 15 | *.tmp 16 | *.bak 17 | *.swp 18 | *~.nib 19 | local.properties 20 | .classpath 21 | .settings/ 22 | .loadpath 23 | 24 | # External tool builders 25 | .externalToolBuilders/ 26 | 27 | # Locally stored "Eclipse launch configurations" 28 | *.launch 29 | 30 | # CDT-specific 31 | .cproject 32 | 33 | # PDT-specific 34 | .buildpath 35 | 36 | 37 | ################# 38 | ## Visual Studio 39 | ################# 40 | 41 | ## Ignore Visual Studio temporary files, build results, and 42 | ## files generated by popular Visual Studio add-ons. 43 | 44 | # User-specific files 45 | *.suo 46 | *.user 47 | *.sln.docstates 48 | 49 | # Build results 50 | [Dd]ebug/ 51 | [Rr]elease/ 52 | *_i.c 53 | *_p.c 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.vspscc 68 | .builds 69 | *.dotCover 70 | 71 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 72 | #packages/ 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opensdf 79 | *.sdf 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | 85 | # ReSharper is a .NET coding add-in 86 | _ReSharper* 87 | 88 | # Installshield output folder 89 | [Ee]xpress 90 | 91 | # DocProject is a documentation generator add-in 92 | DocProject/buildhelp/ 93 | DocProject/Help/*.HxT 94 | DocProject/Help/*.HxC 95 | DocProject/Help/*.hhc 96 | DocProject/Help/*.hhk 97 | DocProject/Help/*.hhp 98 | DocProject/Help/Html2 99 | DocProject/Help/html 100 | 101 | # Click-Once directory 102 | publish 103 | 104 | # Others 105 | [Bb]in 106 | [Oo]bj 107 | sql 108 | TestResults 109 | *.Cache 110 | ClientBin 111 | stylecop.* 112 | ~$* 113 | *.dbmdl 114 | Generated_Code #added for RIA/Silverlight projects 115 | 116 | # Backup & report files from converting an old project file to a newer 117 | # Visual Studio version. Backup files are not needed, because we have git ;-) 118 | _UpgradeReport_Files/ 119 | Backup*/ 120 | UpgradeLog*.XML 121 | 122 | 123 | 124 | ############ 125 | ## Windows 126 | ############ 127 | 128 | # Windows image file caches 129 | Thumbs.db 130 | 131 | # Folder config file 132 | Desktop.ini 133 | 134 | 135 | ############# 136 | ## Python 137 | ############# 138 | 139 | *.py[co] 140 | 141 | # Packages 142 | *.egg 143 | *.egg-info 144 | dist 145 | build 146 | eggs 147 | parts 148 | bin 149 | var 150 | sdist 151 | develop-eggs 152 | .installed.cfg 153 | 154 | # Installer logs 155 | pip-log.txt 156 | 157 | # Unit test / coverage reports 158 | .coverage 159 | .tox 160 | 161 | #Translations 162 | *.mo 163 | 164 | #Mr Developer 165 | .mr.developer.cfg 166 | 167 | # Mac crap 168 | .DS_Store 169 | 170 | node_modules -------------------------------------------------------------------------------- /test/https-disable-301s.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai') 2 | , expect = chai.expect 3 | , request = require('request') 4 | , server 5 | , baseurl 6 | , secureBaseurl 7 | , SSLRequiredErrorText 8 | ; 9 | 10 | before(function () { 11 | server = require('./server')({ enable301Redirects: false, httpPort: 8090, httpsPort: 10443 }); 12 | baseurl = 'http://localhost:' + server.port; 13 | secureBaseurl = 'https://localhost:' + server.securePort; 14 | SSLRequiredErrorText = 'SSL Required.'; 15 | }); 16 | 17 | describe('Test HTTPS behavior when 301 redirects are disabled.', function() { 18 | 19 | it('Should be able to get to SSL pages with no issue', function (done) { 20 | var sslEndpoint = secureBaseurl + '/ssl'; 21 | 22 | request.get({ 23 | url: sslEndpoint, 24 | followRedirect: false, 25 | strictSSL: false 26 | }, function (error, response, body) { 27 | //noinspection BadExpressionStatementJS 28 | expect(error).to.not.exist; 29 | expect(response.statusCode).to.equal(200); 30 | expect(body).to.equal('HTTPS only.'); 31 | done(); 32 | }); 33 | }); 34 | 35 | it('Non ssl pages should continue to work normally', function (done) { 36 | request.get({ 37 | url: baseurl, 38 | followRedirect: false, 39 | strictSSL: false 40 | }, function (error, response){ 41 | //noinspection BadExpressionStatementJS 42 | expect(error).to.not.exist; 43 | expect(response.statusCode).to.equal(200); 44 | done(); 45 | }); 46 | }); 47 | 48 | it('Should receive a 403 error on "SSL Only" endpoint when accessed insecurely.', function (done) { 49 | var originalEndpoint = baseurl + '/ssl'; 50 | 51 | request.get({ 52 | url: originalEndpoint, 53 | followRedirect: false, 54 | strictSSL: false 55 | }, function (error, response, body){ 56 | //noinspection BadExpressionStatementJS 57 | expect(error).to.not.exist; 58 | expect(response.statusCode).to.equal(403); 59 | expect(body).to.equal(SSLRequiredErrorText); 60 | done(); 61 | }); 62 | }); 63 | 64 | it('Should successfully POST data to non "SSL Only" endpoint.', function (done) { 65 | var destination = baseurl + '/echo'; 66 | var postData = { key1: 'Keyboard.', key2: 'Cat.'}; 67 | 68 | request.post({ 69 | url: destination, 70 | followRedirect: true, 71 | strictSSL: false, 72 | form: postData 73 | }, function(error, response, body){ 74 | //noinspection BadExpressionStatementJS 75 | expect(error).to.not.exist; 76 | expect(response.statusCode).to.equal(200); 77 | expect(response.request.uri.href).to.equal(destination); 78 | expect(body).to.equal(JSON.stringify(postData)); 79 | done(); 80 | }); 81 | }); 82 | 83 | it('Should receive 403 error when POSTing data to "SSL Only" endpoint.', function (done) { 84 | var destination = baseurl + '/sslEcho'; 85 | var postData = { key1: 'Keyboard.', key2: 'Cat.'}; 86 | 87 | request.post({ 88 | url: destination, 89 | followRedirect: true, 90 | strictSSL: false, 91 | form: postData 92 | }, function(error, response, body){ 93 | //noinspection BadExpressionStatementJS 94 | expect(error).to.not.exist; 95 | expect(response.statusCode).to.equal(403); 96 | expect(response.request.uri.href).to.equal(destination); 97 | expect(body).to.equal(SSLRequiredErrorText); 98 | done(); 99 | }); 100 | }); 101 | 102 | }); -------------------------------------------------------------------------------- /test/x-forwarded-proto-trusted.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai') 2 | , expect = chai.expect 3 | , request = require('request') 4 | , server 5 | , baseurl 6 | , secureBaseurl 7 | , SSLRequiredErrorText 8 | , validHeader 9 | , invalidHeader 10 | ; 11 | 12 | before(function () { 13 | server = require('./server')({ trustXFPHeader: true, httpPort: 8089, httpsPort: 9443 }); 14 | baseurl = 'http://localhost:' + server.port; 15 | secureBaseurl = 'https://localhost:' + server.securePort; 16 | SSLRequiredErrorText = 'SSL Required.'; 17 | 18 | validHeader = { 19 | 'X-Forwarded-Proto': 'https' 20 | }; 21 | 22 | invalidHeader = { 23 | 'X-Forwarded-Proto': 'WrongProtocol' 24 | }; 25 | }); 26 | 27 | describe('Test HTTPS behavior when X-Forwarded-Proto header exists and is trusted.', function(){ 28 | it('Should not be redirected to SSL on non "SSL Only" endpoint.', function(done){ 29 | request.get({ 30 | url: baseurl, 31 | followRedirect: false, 32 | strictSSL: false, 33 | headers: validHeader 34 | }, function (error, response){ 35 | //noinspection BadExpressionStatementJS 36 | expect(error).to.not.exist; 37 | expect(response.statusCode).to.equal(200); 38 | done(); 39 | }); 40 | }); 41 | 42 | it('Should not be redirected to SSL on "SSL Only" endpoint with valid X-Forwarded-Proto Header.', function(done){ 43 | var destination = baseurl + '/ssl'; 44 | request.get({ 45 | url: destination, 46 | followRedirect: false, 47 | strictSSL: false, 48 | headers: validHeader 49 | }, function (error, response){ 50 | //noinspection BadExpressionStatementJS 51 | expect(error).to.not.exist; 52 | expect(response.statusCode).to.equal(200); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('Should get redirect to SSL on "SSL Only" endpoint with invalid X-Forwarded-Proto Header.', function(done){ 58 | var originalDestination = baseurl + '/ssl'; 59 | var expectedDestination = secureBaseurl + '/ssl'; 60 | 61 | request.get({ 62 | url: originalDestination, 63 | followRedirect: false, 64 | strictSSL: false, 65 | headers: invalidHeader 66 | }, function (error, response){ 67 | //noinspection BadExpressionStatementJS 68 | expect(error).to.not.exist; 69 | expect(response.statusCode).to.equal(301); 70 | expect(response.headers.location).to.equal(expectedDestination); 71 | done(); 72 | }); 73 | }); 74 | 75 | it('Should get redirected to expected destination on "SSL Only" endpoint with invalid X-Forwarded-Proto ' + 76 | 'Header.', function(done){ 77 | var originalDestination = baseurl + '/ssl'; 78 | var expectedDestination = secureBaseurl + '/ssl'; 79 | 80 | request.get({ 81 | url: originalDestination, 82 | followRedirect: true, 83 | strictSSL: false, 84 | headers: invalidHeader 85 | }, function (error, response){ 86 | //noinspection BadExpressionStatementJS 87 | expect(error).to.not.exist; 88 | expect(response.statusCode).to.equal(200); 89 | expect(response.request.uri.href).to.equal(expectedDestination); 90 | done(); 91 | }); 92 | }); 93 | 94 | it('Should successfully POST data to "SSL Only" endpoint with valid X-Forwarded-Proto Header.', function(done){ 95 | var destination = baseurl + '/sslEcho'; 96 | var postData = { key1: 'Keyboard.', key2: 'Cat.'}; 97 | request.post({ 98 | url: destination, 99 | followRedirect: true, 100 | strictSSL: false, 101 | form: postData, 102 | headers: validHeader 103 | }, function(error, response, body){ 104 | //noinspection BadExpressionStatementJS 105 | expect(error).to.not.exist; 106 | expect(response.statusCode).to.equal(200); 107 | expect(response.request.uri.href).to.equal(destination); 108 | expect(body).to.equal(JSON.stringify(postData)); 109 | done(); 110 | }); 111 | }); 112 | 113 | it('Should receive 403 error when POSTing data to "SSL Only" endpoint with invalid X-Forwarded-Proto ' + 114 | 'Header.', function(done){ 115 | var destination = baseurl + '/sslEcho'; 116 | var postData = { key1: 'Keyboard.', key2: 'Cat.'}; 117 | request.post({ 118 | url: destination, 119 | followRedirect: true, 120 | strictSSL: false, 121 | form: postData, 122 | headers: invalidHeader 123 | }, function(error, response, body){ 124 | //noinspection BadExpressionStatementJS 125 | expect(error).to.not.exist; 126 | expect(response.statusCode).to.equal(403); 127 | expect(response.request.uri.href).to.equal(destination); 128 | expect(body).to.equal(SSLRequiredErrorText); 129 | done(); 130 | }); 131 | }); 132 | 133 | }); -------------------------------------------------------------------------------- /test/x-forwarded-proto.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai') 2 | , expect = chai.expect 3 | , request = require('request') 4 | , server 5 | , baseurl 6 | , secureBaseurl 7 | , SSLRequiredErrorText 8 | , validHeader 9 | , invalidHeader 10 | ; 11 | 12 | before(function () { 13 | server = require('./server')({ httpPort: 8087, httpsPort: 7443 }); 14 | baseurl = 'http://localhost:' + server.port; 15 | secureBaseurl = 'https://localhost:' + server.securePort; 16 | SSLRequiredErrorText = 'SSL Required.'; 17 | 18 | validHeader = { 19 | 'X-Forwarded-Proto': 'https' 20 | }; 21 | 22 | invalidHeader = { 23 | 'X-Forwarded-Proto': 'WrongProtocol' 24 | }; 25 | }); 26 | 27 | describe('Test HTTPS behavior when X-Forwarded-Proto header exists but is not trusted.', function(){ 28 | it('Should not be redirected to SSL on non "SSL Only" endpoint.', function(done){ 29 | request.get({ 30 | url: baseurl, 31 | followRedirect: false, 32 | strictSSL: false, 33 | headers: validHeader 34 | }, function (error, response){ 35 | //noinspection BadExpressionStatementJS 36 | expect(error).to.not.exist; 37 | expect(response.statusCode).to.equal(200); 38 | done(); 39 | }); 40 | }); 41 | 42 | it('Should be redirected to SSL on "SSL Only" endpoint with valid but untrusted X-Forwarded-Proto Header.', 43 | function(done){ 44 | var destination = baseurl + '/ssl'; 45 | request.get({ 46 | url: destination, 47 | followRedirect: false, 48 | strictSSL: false, 49 | headers: validHeader 50 | }, function (error, response){ 51 | //noinspection BadExpressionStatementJS 52 | expect(error).to.not.exist; 53 | expect(response.statusCode).to.equal(301); 54 | done(); 55 | }); 56 | }); 57 | 58 | it('Should be redirect to SSL on "SSL Only" endpoint with invalid untrusted X-Forwarded-Proto Header.', 59 | function(done){ 60 | var originalDestination = baseurl + '/ssl'; 61 | var expectedDestination = secureBaseurl + '/ssl'; 62 | 63 | request.get({ 64 | url: originalDestination, 65 | followRedirect: false, 66 | strictSSL: false, 67 | headers: invalidHeader 68 | }, function (error, response){ 69 | //noinspection BadExpressionStatementJS 70 | expect(error).to.not.exist; 71 | expect(response.statusCode).to.equal(301); 72 | expect(response.headers.location).to.equal(expectedDestination); 73 | done(); 74 | }); 75 | }); 76 | 77 | it('Should be redirected to expected destination on "SSL Only" endpoint with invalid untrusted X-Forwarded-Proto ' + 78 | 'Header.', function(done){ 79 | var originalDestination = baseurl + '/ssl'; 80 | var expectedDestination = secureBaseurl + '/ssl'; 81 | 82 | request.get({ 83 | url: originalDestination, 84 | followRedirect: true, 85 | strictSSL: false, 86 | headers: invalidHeader 87 | }, function (error, response){ 88 | //noinspection BadExpressionStatementJS 89 | expect(error).to.not.exist; 90 | expect(response.statusCode).to.equal(200); 91 | expect(response.request.uri.href).to.equal(expectedDestination); 92 | done(); 93 | }); 94 | }); 95 | 96 | it('Should receive 403 error when POSTing data to "SSL Only" endpoint with untrusted X-Forwarded-Proto Header.', 97 | function(done){ 98 | var destination = baseurl + '/sslEcho'; 99 | var postData = { key1: 'Keyboard.', key2: 'Cat.'}; 100 | request.post({ 101 | url: destination, 102 | followRedirect: true, 103 | strictSSL: false, 104 | form: postData, 105 | headers: validHeader 106 | }, function(error, response, body){ 107 | //noinspection BadExpressionStatementJS 108 | expect(error).to.not.exist; 109 | expect(response.statusCode).to.equal(403); 110 | expect(response.request.uri.href).to.equal(destination); 111 | expect(body).to.equal(SSLRequiredErrorText); 112 | done(); 113 | }); 114 | }); 115 | 116 | it('Should receive 403 error when POSTing data to "SSL Only" endpoint with untrusted invalid X-Forwarded-Proto ' + 117 | 'Header.', function(done){ 118 | var destination = baseurl + '/sslEcho'; 119 | var postData = { key1: 'Keyboard.', key2: 'Cat.'}; 120 | request.post({ 121 | url: destination, 122 | followRedirect: true, 123 | strictSSL: false, 124 | form: postData, 125 | headers: invalidHeader 126 | }, function(error, response, body){ 127 | //noinspection BadExpressionStatementJS 128 | expect(error).to.not.exist; 129 | expect(response.statusCode).to.equal(403); 130 | expect(response.request.uri.href).to.equal(destination); 131 | expect(body).to.equal(SSLRequiredErrorText); 132 | done(); 133 | }); 134 | }); 135 | 136 | }); -------------------------------------------------------------------------------- /test/http.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai') 2 | , expect = chai.expect 3 | , request = require('request') 4 | , server 5 | , baseurl 6 | , secureBaseurl 7 | , SSLRequiredErrorText 8 | ; 9 | 10 | 11 | before(function () { 12 | server = require('./server')({ httpPort: 8080, httpsPort: 8443 }); 13 | baseurl = 'http://localhost:' + server.port; 14 | secureBaseurl = 'https://localhost:' + server.securePort; 15 | SSLRequiredErrorText = 'SSL Required.'; 16 | }); 17 | 18 | describe('Test standard HTTP behavior.', function(){ 19 | 20 | it('Should not be redirected to SSL on non "SSL Only" endpoint.', function(done){ 21 | request.get({ 22 | url: baseurl, 23 | followRedirect: false, 24 | strictSSL: false 25 | }, function (error, response){ 26 | //noinspection BadExpressionStatementJS 27 | expect(error).to.not.exist; 28 | expect(response.statusCode).to.equal(200); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('Should receive a 301 redirect on "SSL Only" endpoint.', function(done){ 34 | var originalDestination = baseurl + '/ssl'; 35 | var expectedDestination = secureBaseurl + '/ssl'; 36 | request.get({ 37 | url: originalDestination, 38 | followRedirect: false, 39 | strictSSL: false 40 | }, function (error, response, body){ 41 | //noinspection BadExpressionStatementJS 42 | expect(error).to.not.exist; 43 | expect(response.statusCode).to.equal(301); 44 | expect(response.headers.location).to.equal(expectedDestination); 45 | done(); 46 | }); 47 | }); 48 | 49 | it('Should end up at secure endpoint on "SSL Only" endpoint.', function(done){ 50 | var originalDestination = baseurl + '/ssl'; 51 | var expectedDestination = secureBaseurl + '/ssl'; 52 | request.get({ 53 | url: originalDestination, 54 | followRedirect: true, 55 | strictSSL: false 56 | }, function (error, response, body){ 57 | //noinspection BadExpressionStatementJS 58 | expect(error).to.not.exist; 59 | expect(response.statusCode).to.equal(200); 60 | expect(response.request.uri.href).to.equal(expectedDestination); 61 | done(); 62 | }); 63 | }); 64 | 65 | /* 66 | I think these next two tests are completely redundant, but someone once opened an issue about this 67 | because they incorrectly configured their express server, so I had to write tests against his use case 68 | to prove this isn't actually a problem. 69 | */ 70 | 71 | it('Should receive a 301 redirect on a deeply nested "SSL Only" endpoint.', function(done){ 72 | var id = 12983498; 73 | var originalDestination = baseurl + '/ssl/nested/route/' + id; 74 | var expectedDestination = secureBaseurl + '/ssl/nested/route/' + id; 75 | request.get({ 76 | url: originalDestination, 77 | followRedirect: false, 78 | strictSSL: false 79 | }, function (error, response, body){ 80 | //noinspection BadExpressionStatementJS 81 | expect(error).to.not.exist; 82 | expect(response.statusCode).to.equal(301); 83 | expect(response.headers.location).to.equal(expectedDestination); 84 | done(); 85 | }); 86 | }); 87 | 88 | it('Should end up at secure endpoint on a deeply nested "SSL Only" endpoint.', function(done){ 89 | var id = 233223625745; 90 | var originalDestination = baseurl + '/ssl/nested/route/' + id; 91 | var expectedDestination = secureBaseurl + '/ssl/nested/route/' + id; 92 | request.get({ 93 | url: originalDestination, 94 | followRedirect: true, 95 | strictSSL: false 96 | }, function (error, response, body){ 97 | //noinspection BadExpressionStatementJS 98 | expect(error).to.not.exist; 99 | expect(response.statusCode).to.equal(200); 100 | expect(response.request.uri.href).to.equal(expectedDestination); 101 | done(); 102 | }); 103 | }); 104 | 105 | it('Should successfully POST data to non "SSL Only" endpoint.', function(done){ 106 | var destination = baseurl + '/echo'; 107 | var postData = { key1: 'Keyboard.', key2: 'Cat.'}; 108 | request.post({ 109 | url: destination, 110 | followRedirect: true, 111 | strictSSL: false, 112 | form: postData 113 | }, function(error, response, body){ 114 | //noinspection BadExpressionStatementJS 115 | expect(error).to.not.exist; 116 | expect(response.statusCode).to.equal(200); 117 | expect(response.request.uri.href).to.equal(destination); 118 | expect(body).to.equal(JSON.stringify(postData)); 119 | done(); 120 | }); 121 | }); 122 | 123 | it('Should receive 403 error when POSTing data to "SSL Only" endpoint.', function(done){ 124 | var destination = baseurl + '/sslEcho'; 125 | var postData = { key1: 'Keyboard.', key2: 'Cat.'}; 126 | request.post({ 127 | url: destination, 128 | followRedirect: true, 129 | strictSSL: false, 130 | form: postData 131 | }, function(error, response, body){ 132 | //noinspection BadExpressionStatementJS 133 | expect(error).to.not.exist; 134 | expect(response.statusCode).to.equal(403); 135 | expect(response.request.uri.href).to.equal(destination); 136 | expect(body).to.equal(SSLRequiredErrorText); 137 | done(); 138 | }); 139 | }); 140 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | express-force-ssl 2 | ================= 3 | Extremely simple middleware for requiring some or all pages 4 | to be visited over SSL. 5 | 6 | 7 | Installation 8 | ------------ 9 | ```` 10 | $ npm install express-force-ssl 11 | ```` 12 | 13 | Configuration 14 | ============= 15 | As of v0.3.0 there are some configuration options 16 | ------------------------------------------------- 17 | 18 | **NEW Settings Option** 19 | ```javascript 20 | app.set('forceSSLOptions', { 21 | enable301Redirects: true, 22 | trustXFPHeader: false, 23 | httpsPort: 443, 24 | sslRequiredMessage: 'SSL Required.' 25 | }); 26 | ``` 27 | 28 | 29 | **enable301Redirects** - Defaults to ***true*** - the normal behavior is to 301 redirect GET requests to the https version of a 30 | website. Changing this value to ***false*** will cause even GET requests to 403 SSL Required errors. 31 | 32 | **trustXFPHeader** - Defaults to ***false*** - this behavior is NEW and will be default NOT TRUST X-Forwarded-Proto which 33 | could allow a client to spoof whether or not they were on HTTPS or not. This can be changed to ***true*** if you are 34 | behind a proxy where you trust the X-Forwarded-Proto header. 35 | 36 | **httpsPort** - Previous this value was set with app.set('httpsPort', :portNumber) which is now deprecated. This value 37 | should now be set in the forceSSLOptions setting. 38 | 39 | **sslRequiredMessage** - Defaults to ***SSL Required.*** This can be useful if you want to localize your error messages. 40 | 41 | Per-Route SSL Settings are now possible 42 | --------------------------------------- 43 | Settings in your forceSSLOptions configuration will act as default settings for your app. However, these values can 44 | be overridden by setting *res.locals* values before the the express-force-ssl middleware is run. For example: 45 | 46 | ```javascript 47 | app.set('forceSSLOptions', { 48 | enable301Redirects: false 49 | }); 50 | 51 | app.get('/', forceSSL, function (req, res) { 52 | //this route will 403 if accessed via HTTP 53 | return res.send('HTTPS only.'); 54 | }); 55 | 56 | function allow301 (req, res, next) { 57 | res.locals.forceSSLOptions = { 58 | enable301Redirects: true 59 | }; 60 | next(); 61 | } 62 | 63 | app.get('/allow', allow301, forceSSL, function (req, res) { 64 | //this route will NOT 403 if accessed via HTTP 65 | return res.send('HTTP or HTTPS'); 66 | }); 67 | 68 | ``` 69 | 70 | 71 | 72 | Examples 73 | ======== 74 | Force SSL on all pages 75 | ---------------------- 76 | ```javascript 77 | var express = require('express'); 78 | var forceSSL = require('express-force-ssl'); 79 | var fs = require('fs'); 80 | var http = require('http'); 81 | var https = require('https'); 82 | 83 | var ssl_options = { 84 | key: fs.readFileSync('./keys/private.key'), 85 | cert: fs.readFileSync('./keys/cert.crt'), 86 | ca: fs.readFileSync('./keys/intermediate.crt') 87 | }; 88 | 89 | var app = express(); 90 | var server = http.createServer(app); 91 | var secureServer = https.createServer(ssl_options, app); 92 | 93 | app.use(express.bodyParser()); 94 | app.use(forceSSL); 95 | app.use(app.router); 96 | 97 | secureServer.listen(443) 98 | server.listen(80) 99 | 100 | ``` 101 | 102 | Only certain pages SSL 103 | ---------------------- 104 | ```javascript 105 | var express = require('express'); 106 | var forceSSL = require('express-force-ssl'); 107 | var fs = require('fs'); 108 | var http = require('http'); 109 | var https = require('https'); 110 | 111 | var ssl_options = { 112 | key: fs.readFileSync('./keys/private.key') 113 | cert: fs.readFileSync('./keys/cert.crt') 114 | ca: fs.readFileSync('./keys/intermediate.crt') 115 | }; 116 | 117 | var app = express(); 118 | 119 | var server = http.createServer(app); 120 | var secureServer = https.createServer(ssl_options, app); 121 | 122 | app.use(express.bodyParser()); 123 | app.use(app.router); 124 | 125 | app.get('/', somePublicFunction); 126 | app.get('/user/:name', somePublicFunction); 127 | app.get('/login', forceSSL, someSecureFunction); 128 | app.get('/logout', forceSSL, someSecureFunction); 129 | 130 | secureServer.listen(443) 131 | server.listen(80) 132 | ``` 133 | 134 | Custom Server Port Support 135 | -------------------------- 136 | If your server isn't listening on 80/443 respectively, you can change this pretty simply. 137 | 138 | ```javascript 139 | 140 | var app = express(); 141 | app.set('forceSSLOptions', { 142 | httpsPort: 8443 143 | }); 144 | 145 | var server = http.createServer(app); 146 | var secureServer = https.createServer(ssl_options, app); 147 | 148 | ... 149 | 150 | secureServer.listen(443) 151 | server.listen(80) 152 | 153 | ``` 154 | 155 | Test 156 | ---- 157 | ``` 158 | npm test 159 | ``` 160 | 161 | Change Log 162 | ========== 163 | **v0.3.2** - Updated README to remove typo. Thanks @gswalden 164 | 165 | **v0.3.1** - Updated README to remove deprecated usage and fix some typos. Thanks @Alfredo-Delgado and @glennr 166 | 167 | **v0.3.0** - Added additional configuration options, ability to add per route configuration options 168 | 169 | **v0.2.13** - Bug Fix, thanks @tatepostnikoff 170 | 171 | **v0.2.12** - Bug Fix 172 | 173 | **v0.2.11** - Updated README to fix usage example typo and formatting fixes 174 | 175 | **v0.2.10** - Updated README for npmjs.com markdown changes 176 | 177 | **v0.2.9** - More modular tests. 178 | 179 | **v0.2.8** - Now sends 403 SSL Required error when HTTP method is anything but GET. 180 | This will prevent a POST/PUT etc with data that will end up being lost in a redirect. 181 | 182 | **v0.2.7** - Additional Test cases. Added example server. 183 | 184 | **v0.2.6** - Added Tests 185 | 186 | **v0.2.5** - Bug Fix 187 | 188 | **v0.2.4** - Now also checking X-Forwarded-Proto header to determine SSL connection 189 | Courtesy of @ronco 190 | 191 | **v0.2.3** - Update README 192 | 193 | **v0.2.2** - Redirect now gives a 301 permanent redirection HTTP Status Code 194 | Courtesy of @tixz 195 | 196 | **v0.2.0** - Added support for ports other than 80/443 for non-secure/secure ports. 197 | For example, if you host your non-ssl site on port 8080 and your secure site on 8443, version 0.1.x did not support it. 198 | Now, out of the box your non-ssl site port will be recognized, and to specify a port other than 443 for your ssl port 199 | you just have to add a setting in your express config like so: 200 | **Update, this method of setting httpsPort is deprecated as of v 0.3.0** 201 | 202 | ````javascript 203 | app.set('httpsPort', 8443); 204 | ```` 205 | and the plugin will check for it and use it. Defaults to 443 of course. 206 | 207 | **v0.1.1** - Bug fix 208 | Courtesy of @timshadel 209 | --------------------------------------------------------------------------------