No release in over a year
Time-safe finders for ActiveRecord's has_secure_token.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

 Project Readme

Has More Secure Token

Find-by-digest extensions for ActiveRecord's has_secure_token.

Installation

Install the gem and add to the application's Gemfile by executing:

$ bundle add has_more_secure_token

If bundler is not being used to manage dependencies, install the gem by executing:

$ gem install has_more_secure_token

Finally, add this to your app/models/application_record.rb:

class ApplicationRecord < ActiveRecord::Base
  include HasMoreSecureToken

  # ...
end

Note: HasMoreSecureToken was developed and tested only with PostgreSQL (major versions 12, 14, and 15).

Usage

To use time-safe lookups with has_secure_token, we specify a find_by_digest with the digest algorithm we wish to use:

class User < ApplicationRecord
  has_secure_token :password_reset_token, find_by_digest: 'sha256'
end

Doing so will override the find_by_{token-name} and find_by_{token-name}! methods (find_by_password_reset_token and find_by_password_reset_token! in the example above) that perform secure finds.

For example, if we now need to locate a User record by a token, we use:

# Read a token from somewhere...
token = params[:password_reset_token]

User.find_by_password_reset_token(token) # => #<User:0x0000...>

And this will produce the following database query:

  User Load (1.9ms)  SELECT "users".* FROM "users" WHERE digest("users"."password_reset_token", 'SHA256') = $1 LIMIT $2  [["password_reset_token", "<32 bytes of binary data>"], ["LIMIT", 1]]

As a nice side-effect, since a binary representation of the digest is used, Rails omits the exact values from our logs (regardless of whether our log filters are configured to match the name of the attribute).

Improving Performance with Indices

HasMoreSecureToken uses binary hashes directly rather than a hex representation, so we can define an index to speed up lookups for the above example as follows:

enable_extension 'pgcrypto' # required for `digest(...)` if not already enabled

create_table :users do |t|
  # We include a unique index to ensure no duplicate tokens are generated
  t.binary :password_reset_token, null: false, index: { unique: true }

  t.index "digest(password_reset_token, 'SHA256')",
          name:  'index_users_on_password_reset_token_digest',
          using: :hash # recommended for PG12 and newer
end

Since we are only concerned with the equality (=) operator, using a HASH index will provide fast lookups without revealing timing information in the same way a BTREE index might.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/mintyfresh/has_more_secure_token. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the HasMoreSecureToken project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.