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: '[email protected]', 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.