Generating Secure Tokens on Your ActiveRecord Models

Generating Secure Tokens on Your ActiveRecord Models

May 31, 2023

You must have used the `has_secure_password` macro in Rails. Did you know Rails also provides a `has_secure_token` macro to generate unique tokens on your models? In this article, we'll learn how it works and we'll also see how Rails implements it behind the scenes.

Sometimes, you may want to use token-based authentication to grant your users access to some resource for some time, after which the token expires and needs to be regenerated.

You could manually create a token on your models as follows:

class User < ApplicationRecord
before_create :generate_token

private

def generate_token
self.token = SecureRandom.base58(24)
end
end

However, a better solution is to use the has_secure_token macro provided by Rails. By default, it will use the name :token and length 24, but you can provide a different name and length for your token.

class User < ApplicationRecord
has_secure_token :access_code, length: 30
end

Here's a passing test that shows how it works.

test 'user has a secure access code which can be regenerated' do
user = create(:user)

assert user.access_code # rpWRC...

assert_equal 30, user.access_code.length

user.regenerate_access_code # J6K9u...
end

Note: If you're using it for the first time, don't forget to create the backing column for your model.

class AddAccessCodeToUser < ActiveRecord::Migration[7.0]
def change
add_column :users, :access_code, :string
end
end

For the most part, you can be assured that the generated token will be unique. However, consider adding a unique index in the database just in case, to deal with this highly unlikely scenario.

Regenerating Tokens

Sometimes, for security reasons, you may want to expire the user's token or access code after some time. This is useful if they haven't been active on your application for a while and you want to make sure the old token isn't compromised.

You can regenerate the token using the regenerate_{token} method on your model.

def handle_inactive_member(user)
user.regenerate_access_code
end

Behind the Scenes

If you open the secure_token.rb file in the Rails codebase, you'll notice that the ActiveRecord::SecureToken module is a Rails Concern.

Concerns in Rails: Everything You Need to Know

Here's a simplified version of the source code.

# activerecord/lib/active_record/secure_token.rb

module ActiveRecord
module SecureToken
extend ActiveSupport::Concern

module ClassMethods
def has_secure_token(attribute = :token, length: MIN_LENGTH)
require "active_support/core_ext/securerandom"

define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(length: length) }

before_create { send("#{attribute}=", self.class.generate_unique_secure_token(length: length)) unless send("#{attribute}?") }
end

def generate_unique_secure_token(length: MIN_LENGTH)
SecureRandom.base58(length)
end
end
end
end

The very first thing to note is that Rails will only load the securerandom library when you actually use this macro. This library is an interface to secure random number generators.

When you call the has_secure_token macro, two things happen:

  1. It dynamically creates the regenerate_token method using the define_method in Ruby. All it does is update the existing token, setting a new value.
  2. Adds a before_create callback that fires before the model is saved. It calls the model.token= method, generating and setting a secure and unique token.

Finally, the generate_unique_secure_token method uses the SecureRandom class to generate a random base58 string with the given length.

That's it.

Sign up for my newsletter

Let's learn to become better developers.

Comments

No comments yet. Be the first to leave one.

Sign in to leave a comment.