has_secure_token for unique random token in Rails 5

Abhishek Jain

By Abhishek Jain

on March 23, 2016

This blog is part of our  Rails 5 series.

We sometimes need unique and random tokens in our web apps. Here is how we typically build it.

1class User < ActiveRecord::Base
2
3  before_create :set_access_token
4
5  private
6
7  def set_access_token
8    self.access_token = generate_token
9  end
10
11  def generate_token
12    loop do
13      token = SecureRandom.hex(10)
14      break token unless User.where(access_token: token).exists?
15    end
16  end
17end

has_secure_token in Rails 5

Rails 5 has added has_secure_token method to generate a random alphanumeric token for a given column.

1
2class User < ApplicationRecord
3  has_secure_token
4end
5

By default, Rails assumes that the attribute name is token. We can provide a different name as a parameter to has_secure_token if the attribute name is not token.

1
2class User < ApplicationRecord
3  has_secure_token :password_reset_token
4end
5

The above code assumes that we already have password_reset_token attribute in our model.

1
2>> user = User.new
3>> user.save
4=> true
5
6>> user.password_reset_token
7=> 'qjCbex522DfVEVd5ysUWppWQ'
8

The generated tokens are URL safe and are of fixed length strings.

Migration helper for generating token

We can also generate migration for token similar to other data types.

1$ rails g migration add_auth_token_to_user auth_token:token
1class AddAuthTokenToUser < ActiveRecord::Migration[5.0]
2  def change
3    add_column :users, :auth_token, :string
4    add_index :users, :auth_token, unique: true
5  end
6end

Notice that migration automatically adds index on the generated column with unique constraint.

We can also generate a model with the token attribute.

1$ rails g model Product access_token:token
1class CreateProducts < ActiveRecord::Migration[5.0]
2  def change
3    create_table :products do |t|
4      t.string :access_token
5
6      t.timestamps
7    end
8    add_index :products, :access_token, unique: true
9  end
10end
11

Model generator also adds has_secure_token method to the model.

1
2class Product < ApplicationRecord
3  has_secure_token :access_token
4end
5

Regenerating tokens

Sometimes we need to regenerate the tokens based on some expiration criteria.

In order to do that, we can simply call regenerate_#{token_attribute_name} which would regenerate the token and save it to its respective attribute.

1
2>> user = User.first
3=> <User id: 11, name: 'John', email: 'john@example.com',
4         token: "jRMcN645BQyDr67yHR3qjsJF",
5         password_reset_token: "qjCbex522DfVEVd5ysUWppWQ">
6
7>> user.password_reset_token
8=> "qjCbex522DfVEVd5ysUWppWQ"
9
10>> user.regenerate_password_reset_token
11=> true
12
13>> user.password_reset_token
14=> "tYYVjnCEd1LAXvmLCyyQFzbm"
15

Beware of race condition

It is possible to generate a race condition in the database while generating the tokens. So it is advisable to add a unique index in the database to deal with this unlikely scenario.

Stay up to date with our blogs. Sign up for our newsletter.

We write about Ruby on Rails, ReactJS, React Native, remote work,open source, engineering & design.