Categories
Programming

Logging Out of a Rails App That Uses session_store = :cookie_store

TL;DR

If you want to be able to destroy a Rails session_store = :cookie_store session from the client, you have to tell Rails not to use an HttpOnly cookie. In Rails 4, add this to your config/initializers/session_store.rb:

YourApp::Application.config.session_store :cookie_store, key: 'your_key_name', httponly: false

…and then destroy the session cookie in JavaScript, e.g.,

deleteCookie = function(name) { document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC"; }

deleteCookie('your_key_name');

TL;RA:

So we’ve been dealing with this problem for about a year now. Our application’s back-end REST API is written in Ruby on Rails, with Devise as our authentication mechanism, and our user-facing EmberJS (Javascript) apps consume the API.

We have been using the Rails :cookie_store, which lets you store all of the session data in an encrypted cookie. The good news about this is that you don’t have to make a database request for the session data every time the user makes a call; the bad news is that it’s very hard to log out, because the only way to destroy the session data is by sending an update from the server itself. In fact this is a widely recognized issue; for more information you can see this blog post.

Now, what we really should do is switch to something like a Redis store or the built-in :active_record_store, just like that blog post recommends. We actually did that in our app, but we experienced problems with users being asked to log in over and over again. Session persistence was an issue, so we had to make a (very Agile) decision to switch back to something that worked. While switching back to a :cookie_store means they users don’t have to log in again and again, it also means that they can’t log out. And we have extra complexity because we have a special Rails controller that handles launching into multiple apps based on user permissions. It’s going to need to be re-written at some point, but in the meantime we needed a fix.

Here’s the background: Rails uses a session cookie with the HttpOnly flag set. HttpOnly is a security feature that means a client (e.g. with Javascript) cannot directly update the contents of a cookie; updates can only come from the server. So you’d think it would be easy to destroy a cookie in Javascript and get around this whole problem of not being able to log out (because deleting the session cookie definitely logs you out). Except we can’t do that because of HttpOnly. Javascript can simply not write to an HttpOnly cookie. This is generally A Good Thing for security, so it’s hard to criticize Rails for making this choice (though I’m sure that won’t stop people from criticizing anyway). There was some discussion about using expiring cookies, etc., for cookie stores at https://github.com/rails/rails/pull/11168, but this comment from the pull request’s author is very telling:

Giving up. The inheritance and mixins approach in the chained and legacy cookie jar code is absurd beyond my patience.

Someone else is free to run with this code as a start and take the credit.

The proper solution is to use something like the :active_record_store or a :redis_store, where your authentication and authorization can just destroy the session in the database or Redis store. Maybe you’re in the situation we are, though, where that isn’t feasible. Well, I has solution.

The good news is that you can tell Rails not to make cookies HttpOnly, and it requires a single change in your initializer. You just need to add httponly: false to the session_store initializer. In Rails 4, add this to your config/initializers/session_store.rb:

YourApp::Application.config.session_store :cookie_store, key: 'your_key_name', httponly: false

Of course, you’ll have to manually destroy the cookie in the browser, or use something like Devise to sign out all users so they can pick up the new non-HttpOnly key. Once you do that, though, you can use any number of Javascript libraries to fiddle with cookies. There are several good ones, but I just wrote a little helper because all I needed to do was delete a cookie, and I didn’t want to import an entirely new library for a function I could write in one line:

deleteCookie = function(name) { document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC"; }

deleteCookie('your_key_name');

Well, mine is in CoffeeScript:

deleteCookie = (name) -> document.cookie = "#{name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC";

Now, when the user logs out, the Javascript app sends a request to the Rails API to log them out, and once the Ajax callback is complete, I destroy the session cookie. Now when the app tries to redirect them to another page, their session cookie is missing, so Rails no longer knows who they are and sends them to the Devise sign in page. Here’s the CoffeeScript we’re using (I would apologize for it, but I love CoffeeScript) that removes the auth token we keep in localStorage and nukes the cookie:

deleteCookie: (name) -> document.cookie = "#{name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC";

_clearSession: ->
  @deleteCookie appSessionCookieName
  localStorage.removeItem('token_name')
  App.set 'token_name', undefined
 
 _redirectToSignin: ->
 window.location.href = '/users/sign_in'
 
actions:
    logout: ->
      Ember.$.ajax('/users/sign_out', { type: 'DELETE' }).then =>
      @_clearSession()
      @_redirectToSignin()

This was a frustrating problem with a simple solution. Keep in mind that you probably shouldn’t be using cookie stores anyway, but if you do, this is a quick fix for log out.

Categories
Biology Ebola virus Ebola virus disease

The Cost of Complacency: Ebola Virus and the Deaths of Thousands

Ebola Virus - Centers for Disease Control and Prevention's Public Health Image Library/Wikimedia
Ebola Virus – Centers for Disease Control and Prevention’s Public Health Image Library/Wikimedia

One week before I wrote this post, a Liberian man named Thomas Duncan showed up at the same Dallas hospital he had come to four days before, except this time he rolled up to Texas Health Presbyterian Hospital in an ambulance. He has Ebola Virus Disease (EVD), which he likely contracted trying to help an EVD victim four days before leaving Liberia to visit family in Dallas. He is currently in critical condition, “fighting for his life.” My thoughts go out to him and his family.

I won’t rehash the details, because by now every person who hasn’t been camping in the wilderness for the last week knows the story.  (If you have been camping, welcome home. You can read NBC News’ timeline of events.)

The indifference and lack of action on this issue has troubled me for months now. It bothers me even more when I see articles like NPR’s No, Seriously, How Contagious is Ebola?

Nothing the article says is incorrect as far as I know – the basic reproduction number (R0) of this outbreak of Zaire Ebola virus (EBOV) is indeed in the neighbourhood of 2.0, and public health experts are saying that it is unlikely that we will see a major outbreak in places with modern infrastructure and health care.

Nigeria is a great example of what can be done with proper response. Nigeria is ranked as one of the worst health care systems in the world. A man caused the outbreak in Nigeria when he hopped on a plane from Monrovia to Lagos, eventually infecting 20 people. Eight people died, while 13 more were infected and survived. However, with fewer than 100 people exposed to the infected man, and basically getting the guy right to the hospital, Nigeria contained the outbreak. Officials were able to do contact tracing, creating a list of everyone who came in contact with the patient and were therefore at risk of contracting EBOV. With this list, they were able to monitor and isolate infected individuals. It has been five weeks since the last case in Nigeria, with no new cases since then. With proper response and infrastructure, the spread of EBOV can be stopped.

Now, that said… Dallas’ initial reaction was not as good as Lagos’, and that should concern us. In Dallas’ case, it is unlikely that any of the airplane passengers who shared the plane with the guy are infected. The epidemiological evidence right now indicates that Ebola virus is only contagious when the carrier is showing symptoms, and Thomas Duncan did not show symptoms until four days after he arrived in the US, approximately eight days after he was likely exposed to the virus. He was fine when he got on the plane; he was fine when he got off the plane.

When Duncan initially went to the hospital, his travel history was collected. He told them that he had just come from Liberia. According to the hospital’s original reports, this travel history was not available to his doctor, and they had rectified the software issue that caused that. However, as of last night the hospital changed its tune and said the doctor did have access to the travel history. And yet they still sent him home. With symptoms. For almost FOUR DAYS, until he was put in an ambulance and returned to the hospital.

Now, the good news is that the US authorities can and have done their own contact tracing, and they are monitoring closely. If there’s an organization that is capable of managing an outbreak, it’s the US CDC. The bad news is that the US’ much-praised health care system failed at the most basic detection and infection management. So keep that in mind when people say that the health care system is immune to an EVD outbreak: the health care system is made up of people and processes, and in this case both the people and the processes failed. More good news, though: I’m 100% certain that this case is causing health care professionals all over the world to be much more vigilant. We had two cases in the Toronto area where people had recently travelled to West Africa, and were isolated once they presented as symptomatic. Both tested negative for EBOV.

So, that’s all of the background for what I am about to say.

This is why the NPR  article really bothers me.

On March 24, the Guinean Ministry of Health and MSF (Medecins Sans Frontieres/Doctors Without Borders) notified the WHO of the outbreak in Guinea, and over the next couple of months started begging for help. Louder and louder. On June 22, MSF said the outbreak was “out of control”. Do you know when the WHO declared an international emergency? August 8.

Nobody in the developed world seemed to care, except to start comforting their own populations that it was unlikely that EBOV would show up in their country. Which is missing the point, because the whole “hey, we’re fine, so why should we care?” attitude is what has led to the international humanitarian crisis we are in now.

If the WHO, and major world governments, had snapped into action, they would have had a chance to contain it. Isolation and contact tracing are key techniques in stopping outbreaks of highly virulent communicable diseases. When there were a few dozen victims in rural areas, this was still possible, as it had been in all past EBOV outbreaks. The virus infects, reproduces itself, and the disease burns itself out by either killing its host or granting them ~10-year immunity if they survive.

We aren’t there any more. Conventional containment methods cannot work. There are simply not enough medical professionals or beds to receive and isolate all of the patients showing up at hospitals and treatment centres. People are vomiting and shitting themselves in taxis on the way to the hospital, where they lie outside (and sometimes die) waiting because the hospital is full. And then what will the next passenger do? There is no way to do contact tracing any more. In the middle of June 2014, EBOV made its way into Monrovia, the capital of Liberia. Monrovia has a population of a million people, many of whom live in close quarters in slums without electricity or running water. Think about what that means for the spread of this virus.

Our selfish complacency, facilitated by articles like this, has led us to a situation where this “modest” R0 of 1.5-2.5 is now doubling infections every three weeks. Every. Three. Weeks.

According to the latest statistics released by WHO, in the last nine days, the number of deaths has increased by 22% and the number of cases by 27-28%. (Well, it’s higher now, because those numbers were as of Friday. I am writing this on Sunday. Keep in mind that those two days make a difference, as we sit around and promise supplies, personnel, and financial aid. Dozens or hundreds more people have started showing symptoms since you and I left work on Friday. And they have no access to medical care until the patient occupying their bed dies or recovers. Think about that for a minute.)

Thousands of people have died; thousands more have been infected; and thousands beyond that have been exposed. The WHO reported ~3400 deaths and ~7500 cases as of October 3. The CDC says these numbers are vastly underreported, perhaps to the tune of 2-2.5x. People don’t go to the hospital, so they are staying at home, infecting the people around them… and in at least 50% of the cases, dying without ever being noticed by the authorities.

The NPR article quotes Thomas Frieden, the director of the CDC, as saying, “I have no doubt that we will control this importation, or case, of Ebola so that it does not spread widely in this country.”

Except here is something else that Frieden said, but the article omitted:

“The outbreak is even worse than I’d feared. … This is a threat not just to West Africa and to Africa, this is a threat to the world.”

Here is what Joanne Liu, the International President of MSF, said to the UN in mid-September:  (And remember how long two weeks is in this uncontained outbreak with a “not that impressive” R0 of 2.0 and a doubling time of  about three weeks.)

“We are unable to predict how the epidemic will spread. We are dealing largely with the unknown. But we do know that the number of recorded Ebola cases represents only a fraction of the real number of people infected. We do know that transmission rates are at unprecedented levels. We do know that communities are being decimated. And, with certainty, we know that the ground response remains totally, and lethally, inadequate.”

“With every passing week, the epidemic grows exponentially. With every passing week, the response becomes all the more complicated.”

While we probably shouldn’t be freaking out about our mom or our partner or or our kid getting EBOV (at least not yet), we should be freaking out about the fact that there’s a whole lot of talk and very little action to support the NGOs who are trying (and failing through no fault of their own, in spite of valiant efforts) to contain this outbreak. They simply cannot do the work that the world is hoping that they will do. By ignoring their pleas for help, we have set them up to fail. This is on us.

The minute migrant workers start moving from West Africa to other currently-unaffected African countries, or heading to other parts of the world like India, the world could be in deep fucking shit.

We need to be shaken out of our complacency – not next month, not next week. Now.

At this point the best case scenariobest case, assuming thousands of beds in isolation wards, and hundreds or thousands of medical professionals showing up with proper equipment and training–is that the outbreak will be over by the middle of 2015. They have no way to get the R0 under 1 right now. The longer it sits above 1, the more people will become infected. The more people will die. Thousands of people. Eventually tens of thousands. Maybe hundreds of thousands. All because we’re wringing our hands, making promises… and operating at the speed of bureaucracy. In the space of a week, an EVD victim can go from sick to dead. A week is really fast for bureaucracy. EBOV doesn’t wait for meetings to be convenient to your schedule.

That’s why we should be freaking out. Not because of our own families who are safe in Germany, Canada, Chile, Japan, and the US, but because of the hundreds of thousands or millions of people who could die because we’re telling ourselves everything will be fine.

Once all of this is over, we need to take a long, hard look at ourselves, and ask ourselves and our governments why we let it get this bad. We must stop it now, and this can never happen again.