Category: Rails

Ruby on Rails: Migrating to Devise from your own authentication engine – Using custom Encryptors

Having your own authentication engine can be fun. You get to know how things work, why you should use salt, pepper, SHA2 instead of MD5 and much more. It also allows you to work with many old systems built before anyone heard about Devise. Still, I must say, that in old, maintained systems, sometimes it is worth throwing your own solution in favour of something that is already out there. Thanks to this, you won't have to support the whole dedicated authentication engine stack (code, tests, docs). I decided to do exactly this: move from my own engine that was maintained for last 6 years to the Devise based authentication.

Moving whole controllers logic is quite simple: you just drop whatever you have and you use Devise stuff ;) but what about your custom, self-build encryption engine? The easiest approach would be to reset all the passwords and ask users to provide a new one again. Unfortunately it is not as user-friendly as we would want it to be. Users might feel afraid that we ask them for their password again not in the sign in process.

Luckily there's a much easier approach: you can just use your current custom encryptor with Devise (as long as it is safe).

To do this, you need to do following things:

  1. Adding devise-encryptable to your Gemfile
  2. Moving your encryption logic to a Devise proper namespace
  3. Setting your encryption engine as a default one for Devise

After that, you should be able to use Devise with any encryption engine you used to.

Adding devise-encryptable to your Gemfile

This is definitely the easiest part. In your Gemfile file just:

gem 'devise'
gem 'devise-encryptable'

and run bundle install.

Moving your encryption logic to a Devise proper namespace

This is the hardest part. Create a file in your initializers (or add it to /config/initializers/devise.rb). It should contain your encryptor inside following modules:

module Devise
  module Encryptable
    module Encryptors
      # Here you should but encryption class that inherits from Base
    end
  end
end

Inside of it, you need to create a class that will correspond to your encrypion engine. It must inherit from Encryptors Base class and should contain one method called digest:

module Devise
  module Encryptable
    module Encryptors
      class CustomAuthentication < Base
        def self.digest(password, stretches, salt, pepper)
        end
      end
    end
  end
end

This method accepts following parameters:

  1. password - password provided by user
  2. stretches - cost for hashing the password (default 10)
  3. salt - salt that should be used for hashing
  4. pepper - pepper that should be used for hashing

Now, once you have all of this, you should just implement your logic in the digest method and return a password hash. For example like this one:

require "digest/sha2"

module Devise
  module Encryptable
    module Encryptors
      class CustomAuthentication < Base
        def self.digest(password, stretches, salt, pepper)
          string_to_hash = "#{pepper}#{salt}#{password.reverse}"
          Digest::SHA2.hexdigest(string_to_hash)
        end
      end
    end
  end
end

Setting your encryption engine as a default one for Devise

Now in your devise.rb config file set:

# Require the `devise-encryptable` gem when using anything other than bcrypt
config.encryptor = :custom_authentication

also keep in mind, that your devise using models should include encryptable options as well:

devise :database_authenticatable,
  :trackable, :encryptable, :confirmable, :recoverable,
  :registerable, :validatable, :lockable, :rememberable,
  :omniauthable, omniauth_providers: [:facebook]

ActiveRecord count vs length vs size and what will happen if you use it the way you shouldn’t

One of the most common and most deadly errors you can make: using length instead of count. You can repeat this multiple times, but you will always find someone who'll use it the way it shouldn't be used.

So, first just to make it clear:

#count - collection.count

  • Counts number of elements using SQL query (SELECT COUNT(*) FROM...)
  • #count result is not stored internally during object life cycle, which means, that each time we invoke this method, SQL query is performed again
  • count is really fast comparing to length
2.1.2 :048 > collection = User.all; nil
 => nil
2.1.2 :049 > collection.count
   (0.7ms)  SELECT COUNT(*) FROM `users`
 => 16053
2.1.2 :050 > collection.count
 => 16053

#length - collection.length

  • Returns length of a collecion without performing additional queries... as long as collection is loaded
  • When we have lazy loaded collection, length will load whole colletion into memory and then will return length of it
  • Might use all of your memory when used in a bad way
  • Really fast when having a eagerly loaded collection
2.1.2 :055 > collection = User.all; nil
 => nil
2.1.2 :056 > collection.length
  User Load (122.9ms)  SELECT `users`.* FROM `users`
 => 16053
2.1.2 :057 > collection = User.all; nil
 => nil
2.1.2 :058 > collection.to_a; nil
  User Load (140.9ms)  SELECT `users`.* FROM `users`
 => nil
2.1.2 :059 > collection.length
 => 16053
2.1.2 :060 > collection.length
 => 16053

#size - collection.size

  • Combines abilities of both previous methods;
  • If collection is loaded, will count it's elements (no additional query)
  • If collection is not loaded, will perform additional query
2.1.2 :034 > collection = User.all; nil
 => nil 
2.1.2 :035 > collection.count
   (0.3ms)  SELECT COUNT(*) FROM `users`
 => 16053 
2.1.2 :036 > collection.count
   (0.3ms)  SELECT COUNT(*) FROM `users`
 => 16053 
2.1.2 :037 > collection.size
   (0.2ms)  SELECT COUNT(*) FROM `users`
 => 16053 
2.1.2 :038 > collection.to_a; nil
  User Load (64.2ms)  SELECT `users`.* FROM `users`
 => nil 
2.1.2 :039 > collection.size
 => 16053 

Why would you even care?

Well it might have a huge impact on your apps performance (and resource consumption). In general if you don't want to care at all and you want to delegate this responsibility to someone else, use #size. If you want to care, then play with it and understand how it works, otherwise you might end up doing something like this:

print "We have #{User.all.length} users!"

And this is the performance difference on my computer (with only 16k users):

       user     system      total        real
count     0.010000   0.000000   0.010000 (  0.002989)
length    0.730000   0.060000   0.790000 (  0.846671)

Nearly 1 second to perform such simple task. And this could have a serious impact on your web app! Keep that in mind.

Copyright © 2025 Closer to Code

Theme by Anders NorenUp ↑