HTTP Basic Authentication using restful_authentication with Rails 1.2

Posted by Ben on September 27, 2007

I ran into a small issue this morning with the restful_authentication plugin. We are developing an application where we need to provide a JSON data feed. The feed is protected using HTTP Basic Authentication. There is currently a bug in the plugin which makes Basic Authentication fail. When using the following curl command, I kept receiving a 406 http code from the server.

curl -X GET --basic -u bcurren:test http://localhost:3000/accounts.js

The reason for the 406 error was two fold: 1. there was a bug in restful_authentication which rejects all username and password combinations, even if they are valid 2. restful_authentication does not respond to js requests, so a 406 error is returned, meaning the request type is not valid.

To fix the bug in restful_authentication, I changed the following:

def login_required
  self.current_user ||= User.authenticate(username, passwd) || :false if username && passwd
  logged_in? && authorized? ? true : access_denied
end

to the following

def login_required
  if self.current_user == :false && username && passwd
    self.current_user = User.authenticate(username, passwd) || :false
  end
  logged_in? && authorized? ? true : access_denied
end

The bug is a result of :false being returned from current_user rather than false or nil. Since :false is a symbol it always evaluates to true when used in conditions. The updated code explicitly test if the current_user == :false

After the bug fix above, I was able to successfully retrieve the JSON feed when supplying a valid username and password. However, when submitting and invalid username and password, the application was return a 406 code rather than a 401. This does not result from a bug in restful_authentication but from a lack of support for js requests out of the box. I simply added the following to the access_deniedmethod in authentication_system.rb.

  accepts.js do
    headers["Status"]           = "Unauthorized"
    headers["WWW-Authenticate"] = %(Basic realm="Web Password")
    render :text => "Could't authenticate you", :status => '401 Unauthorized'
  end

I hope this post helps anyone who is tackling the similar issue in Rails 1.2. I will post about some of the other trail and tribulations of using the to_json method in Rails 1.2 in the near future.

Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

  1. rick Thu, 31 Jan 2008 03:10:11 PST

    Hey, I’m not sure what login_required has to do with your issue. logged_in? checks to see if current_user == :false or if it is a valid logged in user. The 406 comes from the #access_denied method which needs to be updated to handle js requests. Instead of ‘format.xml’, I should use format.any to intercept every other format. In fact, it just came up trying to build an iphone interface to an app I’m working on. Doh.

  2. Ben Thu, 31 Jan 2008 14:22:05 PST

    It’s been a while since I wrote this but if I remember correctly, this is the issue with login_required. The method uses ||= to set self.current_user. This is usually idiomatic ruby but the problem in this case is self.current_user == :false. The symbol :false will always evaluate to true. Try this out in irb :false && true. That will return true since :false evaluates as true. Does that help clarify the bug I found?

    I looked at the most recent version of the plugin and it looks like this code has been refactored. I haven’t looked at it in detail to determine if the problem still exists.

  3. Paul Sat, 23 Feb 2008 22:46:42 PST

    Thank you for posting this! I had a similar problem with restful_authentication on rails2.0. I was using curl for a .json URL and it worked ok (presumably because the basic auth was part of the command line and was being provided with the first request — not waiting for a 401). However, using a browser it would fail with a 406 because the browser wasn’t getting a 401 to present a pop-up authentication box. Adding these lines to authenticated_system.rb got it to work:

    format.json do
    request_http_basic_authentication ‘Web Password’
    end

    Thanks again!

Comments