Session: Question: Reasons for validating path and secure options

Created on 8 Oct 2020  路  8Comments  路  Source: expressjs/session

This is more of an a question rather than issue.
From my casual reading of the source code, it looks like express-session will validate path and protocol in incoming request if those options are used.
https://github.com/expressjs/session/blob/6a128cc5941365d0416d92760328a8e11549eb5d/index.js#L623
https://github.com/expressjs/session/blob/6a128cc5941365d0416d92760328a8e11549eb5d/index.js#L196

My question is is there any valid reason to do this given that browser will only send cookie if all condition are met anyway?

Context

  • Regarding secure option, in many applications ssl termination happens in reverse proxy or load balancer, so incoming request to express server is http. I understand this issue can be avoided by setting x-forwarded-proto header, so its not a big problem now.

  • Regarding cookie path, our reverse proxy (nginx) rewrites path to backend server, so the original request path from browser and and the path in incoming request to express server are different. Can I ask if there is any way to solve this?
    Also incase we need to update path in future, does that mean all users need to be logged out ?

question

All 8 comments

I'd like to second this question with some additional context.

The x-forwarded-proto header is _overwritten_ by Application Load Balancers in AWS (and possibly Google Cloud Platform as load balancers as well), rather than appended. Similarly, examples for configuring Nginx across the web often show something like proxy_set_header X-Forwarded-Proto $scheme;.

The isSecure implementation in expressjs/session treats x-forwarded-proto as a comma-delimited list, which isn't necessarily behavior we observe in common implementations:
https://github.com/expressjs/session/blob/master/index.js#L639-L647

As a result the enforcement of the (sort of murky) de-facto standards can get in the way when working with a deep routing stack.

Consider something like this:

[Client] -https-> [CDN] -https-> [LoadBalancer] -http-> [ProxyService] -http-> [LoadBalancer] -http-> [Express]

In the example above we have SSL termination at the public-facing load balancer and un-encrypted traffic in a private network. Due to the distance between Express and the point where SSL was terminated there are no guarantees that X-Fowarded-Protois going to reflect the protocol between the Client and the public endpoint. At this point there are two options that enable setting a "Secure" cookie:

  1. Use HTTPS throughout the entire private network and terminate at the final load balancer.
  2. Add middleware to the Express app to forge x-forwarded-proto on incoming requests.

I'm certainly not _against_ option 1 as a solution, but I do wonder if it makes sense to enforce here vs allowing the browser to take care of enforcement like @tony-sunny suggests.

Hi, sorry you haven't gotten an answer yet. The issue is that letting the browser check this means that your "secure" cookie is only secure in a single direction (from the client to the endpoint it connects to). There are issues of in-the-wild misconfigurations that accidentally expose a web server over both http and https. When a web browser goes to yoursite.com over http, the session cookie will be sent back in the clear on accident, nullifying almost all the protection a security cookie grants -- it could have been observed over the unencrypted channel on the way from the server to the client.

Because of this issue, the cookie specs use "SHOULD" in that servers should not _send_ a secure cookie over an unencrypted connection. This module implements that part by performing these checks -- not only that, but when we were not doing this, a security research company brought this to our attention as a security vulnerability!

As such, we cannot remove the check without ending up just blacklisted as an "insecure" npm module by these security companies, sorry to say.

The best course of action is the following:

  1. Implement a security channel over your entire communications path (the current industry best practice)
  2. Ensure you are correctly handling things like x-forwarded-proto if you are following the previous generation practice of tls termination / stripping at a gateway.
  3. Just tell Express req.protocol is always https (Object.define(app.request, 'protocol', { value: 'https' })) or something similar.
  4. Use a different module that is not under such scrutiny of security auditing companies which does not implement such a check.

@dougwilson Thanks for the answer. I understand it would be bad idea to remove protocol validation in server. Sorry if I missed it, but I didnt get any answer for second question regarding cookie path. Can I ask if there is any workaround to solve this?

Regarding cookie path, our reverse proxy (nginx) rewrites path to backend server, so the original request path from browser and and the path in incoming request to express server are different. Can I ask if there is any way to solve this?
Also incase we need to update path in future, does that mean all users need to be logged out ?

BTW @mattoberle Regarding proto header, I decided to go with this solution of adding an additional header to save original protocol
https://serverfault.com/a/837333

Hi @tony-sunny regarding cookie path, this module can be app.use'd multiple times for different scopes, so needs to understand which one is relevant. The check only is a beginning of path check. I assume you are setting your path to something other than the default (/), is that right?

Also incase we need to update path in future, does that mean all users need to be logged out ?

You cannot change the path of an already set cookie; this is a web browser limitation, as the path of a cookie is part of the keying (which includes the cookie name), so you will effectively have two cookies with the same name in the web browser. In order to change the path of an existing cookie, you must first delete the cookie from the browser that is set on the old path and then create a new cookie with the new path.

@dougwilson

, this module can be app.use'd multiple times for different scopes, so needs to understand which one is relevant.

I'm not sure if I understand, but I'm only using it a single time in apps root.

I assume you are setting your path to something other than the default (/), is that right?

Yeah, for example, consider the url https://example.com/a. I want to set set session cookie scoped to Path: /a.
But when the request reaches the express server, the path is changed to https://example.com/b/a

I'm not sure if I understand, but I'm only using it a single time in apps root.

Yes, I understand you may be only using a single session for your entire site, but some users will use multiple different sessions, or even a session for only a specific path. For example, you can use the session module for just /admin and the check is there so new session cookies are not getting set when a user accesses /home for example.

Yeah, for example, consider the url https://example.com/a. I want to set set session cookie scoped to Path: /a.
But when the request reaches the express server, the path is changed to https://example.com/b/a

Of course, the issue is that the only method here is that you need to have your Express app understand the full "real path" when dealing with anything that needs to operate on absolute paths (like this module) OR you need your reverse proxy to rewrite paths in _both directions_.

If your reverse proxy has an option for path rewrite in both directions, just configure your Express app to operate as if the cookie path should be /b/a and not /a and the reverse proxy will rewrite the path in the outbound set-cookie header.

If your reverse proxy does not have this option, or you are not able to turn it on for various reasons, you need to tell Express what the original URL for your requests are, and many modules (including this one) will operate off that without issue. You can do this either by hard-coding that into your Express app or having your reverse proxy include this as a separate header, like the common x-original-uri header. If you use the standard x-original-uri pattern, you can add app.use((req, res, next) => { req.originalUrl = req.headers['x-original-uri']; next() }) to communicate that to your app.

@dougwilson Ok, Seems like I'll need to use x-original-uri header. Thanks!

Was this page helpful?
0 / 5 - 0 ratings