I want to generate a token in my controller for a user in the \"user_info_token\" column. However, I want to check that no user currently has that token. Would this code suffice
The cleanest solution I found:
@seller.user_info_token = loop do
token = SecureRandom.urlsafe_base64
break token unless User.exists?(user_info_token: token)
end
And something very clean but with potential duplicates (very few though):
@seller.user_info_token = SecureRandom.uuid
Random UUID probability of duplicates
Edit: of course, add a unique index to your :user_info_token
. It will be much quicker to search for a user with the same token and it will raise an exception if by chance, 2 users are saved at the exact same moment with the exact same token!
Rails 5 comes with this feature, you only need to add to your model the next line:
class User
has_secure_token
end
Since Rails 5 is not releases yet you can use the has_secure_token gem. Also you can see my blog post to see more info about it https://coderwall.com/p/kb97gg/secure-tokens-from-rails-5-to-rails-4-x-and-3-x
If your token is long enough and generated by a cryptographically secure [pseudo-]random number generator, then you do not need to verify that the token is unique. You do not need to generate tokens in a loop.
16 raw source bytes is long enough for this effective guarantee. When formatted for URL-safety, the result will be longer.
# Base-64 (url-safe) encoded bytes, 22 characters long
SecureRandom.urlsafe_base64(16)
# Base-36 encoded bytes, naturally url-safe, ~25 characters long
SecureRandom.hex(16).to_i(16).to_s(36)
# Base-16 encoded bytes, naturally url-safe, 32 characters long
SecureRandom.hex(16)
This is because the probability that the 16-byte or 128-bit token is nonunique is so vanishingly small that it is virtually zero. There is only a 50% chance of there being any repetitions after approximately 264 = 18,446,744,073,709,551,616 = 1.845 x 1019 tokens have been generated. If you start generating one billion tokens per second, it will take approximately 264/(109*3600*24*365.25) = 600 years until there is a 50% chance of there having occurred any repetitions at all.
But you're not generating one billion tokens per second. Let's be generous and suppose you were generating one token per second. The time frame until a 50% chance of even one collision becomes 600 billion years. The planet will have been swallowed up by the sun long before then.
I have many models I apply unique tokens to. For this reason I've created a Tokened
concern in app/models/concerns/tokened.rb
module Tokened
extend ActiveSupport::Concern
included do
after_initialize do
self.token = generate_token if self.token.blank?
end
end
private
def generate_token
loop do
key = SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')
break key unless self.class.find_by(token: key)
end
end
end
In any model I want to have unique tokens, I just do
include Tokened
But yes, your code looks fine too.
Maybe you can do something using the actual time. Then you won't need to check if the token was already used by an user.
new_token = Digest::MD5.hexdigest(Time.now.to_i.to_s + rand(999999999).to_s)
user.user_info_token = new_token
You can try some below tricks to get unique token, its so easy which I used in my project -
CREDIT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
def create_credit_key(count = 25)
credit_key = ""
key = CREDIT_CHARS.length
for i in 1..count
rand = Random.rand((0.0)..(1.0))
credit_key += CREDIT_CHARS[(key*rand).to_i].to_s
end
return credit_key
end
Using digest it is again more easy, here I tried to generate without using any algorithm.