Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails, ruby: does SecureRandom.urlsafe_base64 need to be checked for uniqueness for tokens?

I need unique tokens for users stored in my DB. At the moment when I generate a token I check it for uniqueness in the DB before using it. Is this a test I actually need to perform or am I wasting time?

I've looked at the Ruby 2.0.0 API for SecureRandom and it doesn't clarify if I can "trust" the uniqueness or not.

I know no random value can really be "unique" and there's a finite number of possibilities. But with 32 bits of hex values I feel confident I will never run into the same value again in my app but wanted to ask if anyone knew of a "gotcha" with this situation.

Another consideration is using SecureRandom.uuid but that will essentially be the same situation.

# usage
user.password_reset_token = Generator.unique_token_for_user(:password_reset_token)

# Performs DB query to ensure uniqueness
class Generator
  def self.unique_token_for_user(attribute)
    begin
      token = SecureRandom.urlsafe_base64(32)
    end while User.exists?(attribute => token)

    token
  end
end
like image 278
Dan L Avatar asked Apr 07 '14 18:04

Dan L


3 Answers

SecureRandom.uuid generates uuids. A UUID is 128 bits long, and can guarantee uniqueness across space and time. They are designed to be globally unique, unlike urlsafe_base64. See RFC4122.

like image 145
steenslag Avatar answered Nov 19 '22 02:11

steenslag


It doesn't ensure uniqueness but, as svoop said, it's extremely unlikely that you'll get the same result twice.

My advice is: if all you need are random, unique and unguessable tokens, and you don't have hundreds of thousands of users, then use it without worrying.

If you absolutely want unique tokens (e.g. there is some legal requirement), then combine a unique field associated with the user (e.g. the user email) and a random salt, and hash the result.

A naive implementation would be:

require 'securerandom'
require 'digest/md5'


def generate_user_token(user)
  digest(user.email + random_salt)
end


def random_salt
  SecureRandom.urlsafe_base64
end


def digest(string)
  Digest::MD5.hexdigest string
end
like image 30
tompave Avatar answered Nov 19 '22 01:11

tompave


No, you won't see a duplicate in your lifespan.

32 is the length (in bytes) of the random number generated before it get's converted to an urlsafe base64 string, so chances of a duplicate are roughly 1 to 10'000'000'000'000'000'000'000'000'000'000. That's 10e31 and the universe is only 43e17 seconds old.

like image 4
svoop Avatar answered Nov 19 '22 02:11

svoop