I recently split a Rails app into one Ruby app and a set of Clojure apps. The authentication in the Rails app was implemented using the Sorcery plugin, which defaults to BCrypt, so porting to Clojure is relatively easy, since BCrypt is a standard crypto method with implementations available on most platforms.
But as always with magic Rails plugins, there was a twist :)
The solution
This is the Clojure code I ended up with:
(BCrypt/checkpw (str password old-rails-salt) old-rails-password-hash)
old-rails-salt
is the contents of the User#salt
from the Rails app. Also, old-rails-password-hash
is the contents of the User#crypted_password
.
Normally, all you need to do is to pass the password and the BCrypt password hash. The BCrypt hash already includes a salt as part of its contents, so there's no need for an additional salt. For some reason, Sorcery also salts the password by appending User#salt
to the password before hashing it.
My theory is that this is an artifact of the modularity of Sorcery. When Sorcery uses SHA1, it needs to salt, and Sorcery isn't coded so that salting the password is optional. And it doesn't hurt usability on the Rails end, since sorcery is very automagic. As we can see, it's less than ideal when we want to use Sorcery crypto outside of Sorcery, so it's not very portable. But I'm not gonna complain, I guess it's rare to move from Sorcery to another auth system, or even another platform altogether, so they just haven't fixed it. I should probably stop complaining and submit a patch.
The full code:
(defn valid-password?
[user password]
(if-let [old-rails-salt (:old_rails_salt user)]
(BCrypt/checkpw (str password old-rails-salt) (:password_hash user))
(BCrypt/checkpw password (:password_hash user))))
Only users imported from the Rails database will have old_rails_salt
set on them, newly created users will not.