Wednesday, December 14, 2016

Nodejs Express enabling HSTS and cookie secure attribute


To enable HSTS (adds Strict-Transport-Security header to the response) for Node + Express using Helmet, this should be sufficient to add to your app.js:

var hsts = require('hsts')

app.use(hsts({
  maxAge: 15552000  // 180 days in seconds
}))

But a few things didn't work for me. First of all I had to add the force: true attribute. Also I was using an older version of HSTS ([0.14.0 instead of current latest release 2.0.0), so the maxAge was not in seconds but in milliseconds. It says in the README at github but I hadn't checked the version we were using...

To enable the 'secure' attribute for cookies (so they are only sent over HTTPs), I added the cookie-session module. But there is an issue with the secure flag. See the code below for more details.

Below is a full viewcounter.js application that shows the result for enabling hsts and secure cookes. Note also the comments in the code.

Start it with: node viewcounterhelmet.js

viewcounterhelmet.js:

var cookieSession = require('cookie-session')
var helmet = require('helmet');
var express = require('express')

var app = express()

app.use(helmet());
// Works, makes in response show Cache-Control, Pragma, Expires: app.use(helmet.noCache());
var oneYearInSeconds = 31536000;
app.use(helmet.hsts({
  maxAge: oneYearInSeconds,
  includeSubDomains: true,
  force: true
}));

var expiryDate = Date.now() + 60 * 60 * 1000; // 1 hour in milliseconds
app.use(cookieSession({
  name: 'session',
  secret: '10dfaf09-cf6f-43a9-b40b-4eaacbcceb8a',
  maxAge: expiryDate,
  // Not setting secure attr does not show it Secure
  // secure : true, // Set to true and no cookies at all in response. Created ticket for this for v1.2.0
  // secure : false, // Set to false it indeed doesn't show the Secure value with the cookie; it does show the cookies
  secureProxy: true, // Since this is running behind a proxy. But deprecated when using 2.0.0-alpha! Says to use secure option but that stops passing on cookies. When set to true, the cookie is set to Secure. If commented out, cookie not set to Secure
  httpOnly: true  // Don't allow javascript to access the cookie
}))

app.get('/', function (req, res, next) {
  // Update something in the session, needed for a cookie to appear
  req.session.views = (req.session.views || 0) + 1

  // Write response
  res.end(req.session.views + ' views')
})

app.listen(3000)

So when issuing the following curl command, this is the result:

vagrant$ curl -c - -v http://localhost:3000/
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:3000
> Accept: */*
< HTTP/1.1 200 OK
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-Download-Options: noopen
< X-XSS-Protection: 1; mode=block
< Surrogate-Control: no-store
< Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate
< Pragma: no-cache
< Expires: 0
< Strict-Transport-Security: max-age=31536; includeSubDomains
* Added cookie session="eyJ2aWV3cyI6MX0=" for domain localhost, path /, expire 2961374488
< Set-Cookie: session=eyJ2aWV3cyI6MX0=; path=/; expires=Sun, 04 Nov 2063 04:01:28 GMT; secure; httponly
* Added cookie session.sig="DJaPtrG-tmTnVr33fOWXqWGnVlw" for domain localhost, path /, expire 2961374488
< Set-Cookie: session.sig=DJaPtrG-tmTnVr33fOWXqWGnVlw; path=/; expires=Sun, 04 Nov 2063 04:01:28 GMT; secure; httponly
< Date: Fri, 02 Dec 2016 13:30:45 GMT
< Connection: keep-alive
< Content-Length: 7
<
* Connection #0 to host localhost left intact
1 views# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

#HttpOnly_localhost     FALSE   /       TRUE    2961374488      session eyJ2aWV3cyI6MX0=
#HttpOnly_localhost     FALSE   /       TRUE    2961374488      session.sig     DJaPtrG-tmTnVr33fOWXqWGnVlw

Notice the Strict-Transport-Security header and the set cookies with the 'secure' attribute set.

But when setting secure:true (also for the currently latest version 1.2.0), this is the result:

vagrant$ curl -c - -v http://localhost:3000/
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:3000
> Accept: */*
< HTTP/1.1 200 OK
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-Download-Options: noopen
< X-XSS-Protection: 1; mode=block
< Surrogate-Control: no-store
< Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate
< Pragma: no-cache
< Expires: 0
< Strict-Transport-Security: max-age=31536; includeSubDomains
< Date: Tue, 13 Dec 2016 11:27:37 GMT
< Connection: keep-alive
< Content-Length: 7
* Connection #0 to host localhost left intact

So no cookies anymore, while they should be there!  Reported issue here.

Versions used:
  • node js: 6.7.0
  • express: 4.13.30
  • cookie-session: 1.2.0
  • helmet: 0.14.0




No comments: