Skip to main content

Ruby: Using reset_session in Rails with Cucumber and Webrat

I filed the following bug:
All the Rails security guides say that you should call reset_session after the user logs in or logs out. This clears out the session and forces a new session ID to be created. It seems there have been a few Rails bugs related to reset_session over the years. However, I'm now worried that there's a conflict somewhere related to Rails' testing infrastructure and/or Webrat.

In my login action, I call reset_session and then put a nice message in flash. When I actually use the website, I can see (via Firefox) that I'm getting a new session ID, and I can see my flash message. However, when I write tests for those two things, the flash message gets lost, and I don't get a new session ID in my cookies. It's almost as if the new session is being ignored, and the old session is being used.

I'm sorry, but I can't tell exactly where the problem is. I know that there's special Rails code that handles session cookies when you're doing integration testing.

I'm using actionpack-2.3.8 and webrat-0.7.1.
Here's how I did my best to work around the problem.

First of all, I'm storing my sessions in the database. Hence, I created an ActiveRecord model to manually delete session records:
class Session < ActiveRecord::Base
# Rails' reset_session has been nothing but trouble for us. I'm taking
# matters into my own hands. This code will have to change if we stop
# putting our sessions in the database.
def self.nuke_session(session_id)
find_by_session_id!(session_id).destroy
end
end
Since I'm using authlogic, my logout action looks something like this:
def destroy
current_user_session.destroy

# Avoid session fixation attacks. This may seem redundant, but it was
# necessary to make the tests pass.
session[:test_that_this_disappears] = 'ok'
session_id = cookies[ActionController::Base.session_options[:key]]
Session.nuke_session(session_id)
reset_session

redirect_to :action => :logged_out
end
My login action is something like this:
def create
@user_session = UserSession.new(params[:user_session])
unless @user_session.valid?
return render :action => :new
end

# Avoid session fixation attacks by resetting the session.
reset_session
@user_session = UserSession.new(params[:user_session])
@user_session.save!

# For some reason, flash, reset_session, Cucumber, etc. don't get along,
# so I have to pass the flash message via a parameter.
redirect_to(root_url(:login => 1))
end
Instead of setting a message via flash, I pass a query parameter.

I check for this parameter in my ApplicationController:
before_filter :check_for_login_message
...
# For some reason, flash, reset_session, Cucumber, etc. don't get along.
# Hence, after login, we have to pass the flash message via a query parameter
# rather than via flash.
#
# If I have to do this kind of thing again, I'll create a lookup table full
# of messages.
def check_for_login_message
flash.now[:success_message] = "Login successful!" if params[:login]
end
Finally, I have this in a feature file:
# This test should work.  In fact, it does work when you use your browser.
# However, there's a bug somewhere between Rails and Webrat that prevents
# it from working when you use Cucumber. Somehow, reset_session is broken.
#
# Scenario: logging in should invalidate your session cookie
# Given I am on the homepage
# When I look at my session cookie
# And I am logged in
# Then I should have a different session cookie
That test relies on these steps:
# This test should work.  In fact, it does work when you use your browser.
# However, there's a bug somewhere between Rails and Webrat that prevents
# it from working when you use Cucumber. Somehow, reset_session is broken.
#
# Given /^I look at my session cookie$/ do
# @cookie = cookies[ActionController::Base.session_options[:key]]
# end
#
# Then /^I should have a different session cookie$/ do
# new_cookie = cookies[ActionController::Base.session_options[:key]]
# new_cookie.should_not == @cookie
# end

Comments