Logging Out While Offline With HttpOnly Cookies

HttpOnly cookies, PWAs, express, express-session, passport, and logging out

Thomas Brown
2 min readOct 17, 2019

Unlike normal cookies, HttpOnly cookies are designed to prevent Javascript access. This makes them useful for mitigating the effects of XSS attacks.

Enabling them for the session cookie on my site is an easy win. If you’re like me, you didn't think twice about this until it came time to implement your logout functionality. Without the ability to invalidate the session cookie via Javascript, the user must be online in order to logout. Given that offline use is one of the key tenants of a PWA, this is extremely awkward:

Animated screengrab of a user attempting to log out of an aplication. The logout fails because the user is not online.
Nonsense

One recommendation for addressing this problem is:

  1. Use two cookies: one cookie that is HttpOnly and one that is not.
  2. Require both cookies for authentication.
  3. If the user wants to log out while offline, delete the non-HttpOnly cookie and clear your app’s internal state.

This solution gives you the security benefits of HttpOnly cookies without the awkwardness of only allowing online users to logout.

Great! But it’s not immediately clear how this might be implemented given the soup of npm modules I’m using to run my app and do my auth. Assuming you already have a server set up like this (express, express-session, passport), here is my solution:

First, create that non-HttpOnly cookie when a user first authenticates. This cookie will contain a token; this token is also stored in the session. Though it’s not required by recent versions of express-session, the cookie-parser middleware module is needed to allow creation of this additional cookie.

Second, add middleware that pulls that token out of the session and compares it to the one in the cookie. If it doesn’t match, log the user out. This authentication needs to be called from somewhere where we have session access, so after app.use(passport.session()), but before any of your routes that require authorization.

For deployed apps, this implementation has the added benefit of letting you keep your existing sessions. If a non-HttpOnly token is not stored in the session, don’t enforce the second cookie. Instead, generate a non-HttpOnly cookie for the authenticated user so we can “upgrade” this user to our new-and-improved two cookie model.

On the client side, when the user logs out, simply delete the non-HttpOnly cookie:

document.cookie = 'secondaryAuthToken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';

And presto:

--

--