Node.JS Single Page Apps — handling cookies disabled mode


Cookies since their advent have been an integral part of web applications. Since the underlying web HTTP protocol is stateless, cookies provide a nifty way to carry state full information about the user to the web server. With the rise of the Single Page Application (SPA), cookies have become even more instrumental to provide for a state full front end communicating with a stateless backend. Cookies are commonly used for user authentication, experience customization, tracking users across multiple visits etc.

All modern browsers today by default enable cookies to be set by the various domains. However there may be cases in which the user could have disabled setting cookies or some HTTP clients such as certain web-views might not allow cookies by default. In order to make sure that the users who access your website from such clients are still able to get a seamless experience, it becomes imperative to handle the “Cookies Disabled Mode”.

This was one of our major considerations while re-architecting the Checkout flow at PayPal last year. How did we solve it? Memcookies!

Let’s break down the problem statement into its constituents:

  1. We need an alternative to the browser’s cookie jar to persist the cookies in the front end.
  2. We need a mechanism to send these cookies as part of every AJAX request to the server and to ensure that the new cookies being set in the response are again persisted in the alternative storage (mentioned in 1 above).

For the first one since our app is a JavaScript powered heavy weight SPA the ideal location for the persisted cookies is in the Front End JS context. Hence we wrote a client side JS script that took care of persisting the cookies onto the front-end.

For the second, the client side JS uses a custom HTTP header to send across all the cookies as part of every AJAX request from the client. We ended up writing a Node.JS middleware that intercepts every incoming request, reads the custom header, extracts all the cookies and populates them in the Cookies header for downstream middleware to consume.

For setting the response cookies the middleware again fires whenever the response headers are being sent and it reads all Set-Cookie headers, extracts the cookie name-values and populates it in the custom HTTP header that out client side script understands.

Note that this approach has certain limitations though:

  1. This approach works only for AJAX requests and not full page POST since it relies on being sent through custom HTTP headers, and being persisted in memory on a single page.
  2. Using the existing cookie header is off limits. It is not possible to set this header for AJAX requests, nor is it possible to read cookies for a response when they are set as HttpOnly. As such, we use a xcookies header instead.
  3. We do not want javascript to have free reign over these cookies, given that most of them are designed to be HttpOnly. Hence, we encrypt all cookies on the way out, and decrypt them on the way back in. So cookies are now transparently handled on the front-end.
  4. We rely on the front-end to tell us when it’s in cookies disabled mode.
  5. For the initial page render, we provide res._cookies for the renderer to drop on the page. The alternative is making an additional AJAX request, in order to get the xcookies object in headers.
  6. Since the browser can no longer expire cookies on outgoing responses, we can instead do this on incoming requests, by encoding the expiry time into the encrypted cookie value. Also, we can set a hard ceiling on this expiry time of 20 minutes, given that the cookies are only intended to exist until the user is redirected from the page.

Using this approach of persisting cookies in client side JS context and sending them to the backend server via HTTP headers has scaled pretty well for us. This middleware can ideally be used as a supplement prior to any cookie parser middleware so as to enable reading and setting cookies by the app even for cookie disabled browsers.

Credits to Daniel Brain who is the original author of memcookies.

You can checkout the memcookies middleware and the bundled client side JS along with their usage in the above linked repo. Pull Requests/Issues are welcome for suggestions and (or) improvements.