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.
class User < ActiveRecord::Base
before_create :set_access_token
private
def set_access_token
self.access_token = generate_token
end
def generate_token
loop do
token = SecureRandom.hex(10)
break token unless User.where(access_token: token).exists?
end
end
end
Rails 5 has added has_secure_token method to generate a random alphanumeric token for a given column.
class User < ApplicationRecord
has_secure_token
end
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
.
class User < ApplicationRecord
has_secure_token :password_reset_token
end
The above code assumes that we already have password_reset_token
attribute in
our model.
>> user = User.new
>> user.save
=> true
>> user.password_reset_token
=> 'qjCbex522DfVEVd5ysUWppWQ'
The generated tokens are URL safe and are of fixed length strings.
We can also generate migration for token similar to other data types.
$ rails g migration add_auth_token_to_user auth_token:token
class AddAuthTokenToUser < ActiveRecord::Migration[5.0]
def change
add_column :users, :auth_token, :string
add_index :users, :auth_token, unique: true
end
end
Notice that migration automatically adds index on the generated column with unique constraint.
We can also generate a model with the token attribute.
$ rails g model Product access_token:token
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :access_token
t.timestamps
end
add_index :products, :access_token, unique: true
end
end
Model generator also adds has_secure_token
method to the model.
class Product < ApplicationRecord
has_secure_token :access_token
end
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.
>> user = User.first
=> <User id: 11, name: 'John', email: '[email protected]',
token: "jRMcN645BQyDr67yHR3qjsJF",
password_reset_token: "qjCbex522DfVEVd5ysUWppWQ">
>> user.password_reset_token
=> "qjCbex522DfVEVd5ysUWppWQ"
>> user.regenerate_password_reset_token
=> true
>> user.password_reset_token
=> "tYYVjnCEd1LAXvmLCyyQFzbm"
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.
If this blog was helpful, check out our full blog archive.