HTTP Basic Authentication using restful_authentication with Rails 1.2
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.
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.
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.
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!