This feature is scheduled for: Rails v2.3
Long ago, in your mother’s version of rails, we got a http basic authentication plugin. That functionality has since been rolled into Rails core, but it was always lacking HTTP digest authentication. Until this commit, that is.
For those that may now know the difference, basic authentication only base 64 encodes the authenticating username and password (making it easily decoded) whereas digest authentication sends an MD5 hash of your username and password. To simplify, digest is more secure than basic.
To request digest authentication in Rails, you’ll need to be able to retrieve the cleartext password for a given user (so the framework can hash and compare it using the nonce it created specifically for that request). This commit now allows you to also use a specific hashed format of the password. Here’s how this works if you have access to a cleartext password:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class ArticlesController < ApplicationController before_filter :digest_authenticate def digest_authenticate # Given this username, return the cleartext password (or nil if not found) authenticate_or_request_with_http_digest("Articles Administration") do |username| User.find_by_username(username).try(cleartext_password) end end end |
Most of us will want to do something with the result of the authentication and can do so with the boolean return value of authenticate_or_request_with_http_digest:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class ArticlesController < ApplicationController before_filter :digest_authenticate def digest_authenticate success = authenticate_or_request_with_http_digest("Admin") do |username| (@user = User.find_by_username(username)).try(cleartext_password) end # If authentication succeeds, log the user in. If not, kick back out a failure # message as the response body if success session[:user_id] = @user.id else request_http_digest_authentication("Admin", "Authentication failed") end end end |
If you don’t want to store clear text passwords you can return an MD5 hash from the authenticate_or_request_with_http_digest block as long as it’s in the format username:realm:password. You can get a password hash by using Digest::MD5::hexdigest.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class User < ActiveRecord::Base attr_accessor :password validates_presence_of :username, :crypted_password before_save :hash_password ... def hash_password if password_changed? self.crypted_password = Digest::MD5::hexdigest([username, "UserRealm", password].join(":")) end end end |
and then in your controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class ArticlesController < ApplicationController before_filter :digest_authenticate def digest_authenticate # Just return the crypted (hashed) version of the password if it's in the supported # format. Note that the realm here "UserRealm" should match the middle # argument of your password hash success = authenticate_or_request_with_http_digest("UserRealm") do |username| (@user = User.find_by_username(username)).try(crypted_password) end ... end end |
So there you have it, digest authentication in edge Rails.
tags: ruby, rubyonrails

Clear text password in db is bad. Why not assume that the password is stored as md5 and not rehash again? Better than clear text!
This feature is sweet and most welcome !
I agree that storing plaintext is bad. I looked at the implementation a couple of weeks ago, and it looked like it was trying to share too much of the implenation for basic auth. Apache’s digest implemention does just store the hash called ha1 = hash(user:realm:password) along with the username. (p. 241 of RESTful Web Services by Leonard Richardson & Sam Ruby). Hopefully someone (I know, I should try it :) , will try to fix this before 2.3
Rails shouldn’t need to store the plain text password: http://unixpapa.com/auth/basic.html#sec2.2
Storing the password in cleartext may not make as much difference, since if the password file/database is compromised, the attacker could use the ha1 hash to impersonate the user: http://www.faqs.org/rfcs/rfc2617.html#4.13%20Storing%20passwords : The security implications of this are that if this password file is compromised, then an attacker gains immediate access to documents on the server using this realm. Unlike, say a standard UNIX password file, this information need not be decrypted in order to access documents in the server realm associated with this file. On the other hand, decryption, or more likely a brute force attack, would be necessary to obtain the user’s password. ...
Is it still advisable to use MD5 for this or should other hash algorhythms be used after MD5 has come under increasing fire due to security flaws?
http://en.wikipedia.org/wiki/MD5#Vulnerability
Found a problem with the implementation. Added a patch to the ticket at Github for it, which has a one line fix:
http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/1848-http-digest-authentication-broken
Password is cleartext: I haven’t looked at the implementation, but it should be possible to store a hash in the database instead of cleartext (referred to as HA1 in the spec). At least that’s what the RFC requires. This isn’t foolproof, because a compromise could still allow an attacker to potentially use the hash. But still better than cleartext. If this implementation doesn’t support this approach, then that’s a pretty big flaw.
MD5 vs. (SHA-1 or something else): This is really a limitation in the RFC, not the implementation. RFC 2617 requires MD5, but does not require support for any other hashing algorithms. So a server side implementation (like this) can’t assume a client (like a browser) will understand how to execute the protocol with a different algorithm. You can add support for other algorithms as an extension, but there is no agreed upon token to identify the other algorithms in the spec.
Yeah, I’ve already modified code to use the digest hash or a plaintext password. Personally, I think a hashed version should be the default, with an option to use plaintext if necessary. I plan to submit a patch this weekend for it.
Hi Don,
I saw the previous patch got committed. I’m really interested in the change you were working on to support using the digest hash rather than the plaintext password. Did you get a chance to submit that?
Mark, Yes, it is at:
http://rails.lighthouseapp.com/projects/8994/tickets/2000-patch-for-http-digest-authentication-uri-comparison#ticket-2000-6
Latest version of patch is: http://rails.lighthouseapp.com/projects/8994/tickets/2209
Feel free to test it and +1.
Patch was accepted. Digest authentication will now support either a clear text password, or a password stored as the MD5 hash of: username:realm:password
It does require the use of the secret key stored in the session_store.rb initializer to hash the nonce. It should now also work with cookie-less connections.
Hey Donald, I updated the post with the details of the new hashed password support. Let me know if I messed anything up!
I just started a fresh rails 2.3.2 project and got the above example in place which works fine for GET and POST. Then when I started oing PUT and DELETE requests the htaccess popup apears and don’t allow to do the request even if I have the right username/password.
Any idea why?
Cheers Mattias
@Mattias—I ran into the same problem. From debugging it looks like the browser is sending the password encoded for a POST, but Rails is generating the expected password based on a PUT. See ActionController::HttpAuthentication::Digest.expected_response().
Are there any updates on this – I’d like to switch to Rails 2.3.2 and use digiest authentication – at least for the admin area.