Testing HTTP Digest Authentication in Rails
Rails 2.3 introduced HTTP Digest authentication to go along with HTTP Basic as simple ways to authenticate access to your application. HTTP Digest is more secure than Basic for several reasons. First, no passwords are transmitted in cleartext (Basic only Base64 encodes them — there is no encryption). Second, the content of the HTTP_AUTHORIZATION header is tied to a request method and URI. Third, there is a limited window of time an attacker could re-use the header in a replay attack even against the same request method and URI (five minutes in 2.3.2).
These are compelling reasons to switch to Digest from Basic, but there is one problem. How do you test it? For good test coverage, clearly you want to verify that actions that should be protected are, and that only valid username/password combinations permit access.
Basic is easy (in test_helper.rb):
def authenticate_with_http_basic(user = 'one', password = 'one') @request.env['HTTP_AUTHORIZATION'] = "Basic #{Base64.encode64("#{user}:#{password}")}" end |
Try this for Digest (outside of everything else in ActiveSupport::TestCase):
require 'digest/md5' class ActionController::TestCase def authenticate_with_http_digest(user = 'admin', password = 'admin', realm = 'Application') unless ActionController::Base < ActionController::ProcessWithTest ActionController::Base.class_eval { include ActionController::ProcessWithTest } end @controller.instance_eval %Q( alias real_process_with_test process_with_test def process_with_test(request, response) credentials = { :uri => request.env['REQUEST_URI'], :realm => "#{realm}", :username => "#{user}", :nonce => ActionController::HttpAuthentication::Digest.nonce, :opaque => ActionController::HttpAuthentication::Digest.opaque, } request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Digest.encode_credentials( request.request_method, credentials, "#{password}", false ) real_process_with_test(request, response) end ) end end |
Now, precede a call to get, post, put or delete with a call to authenticate_with_http_digest. The code above monkeypatches process_with_test to set up the proper HTTP_AUTHORIZATION header, but only for the current @controller, then runs the request normally.
Oh man, I’ve spent an hour trying to test the HTTP digest auth. I found your post after I read your notes and the lighthouse ticket stating that digest auth is broken when using REST actions PUT and DELETE.
Thank you. This was exactly what I needed.
Here’s an update for Rails 3: https://gist.github.com/790117
Bah, nevermind. More things have changed than I thought. Disregard the above.
I modified the patch to work with Rails 3 (3.0.7 in my case).
https://gist.github.com/1282275