Generating unique token on the fly with Rails

前端 未结 6 1457
醉话见心
醉话见心 2021-01-31 18:50

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

相关标签:
6条回答
  • 2021-01-31 19:38

    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!

    0 讨论(0)
  • 2021-01-31 19:47

    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

    0 讨论(0)
  • 2021-01-31 19:48

    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.

    0 讨论(0)
  • 2021-01-31 19:48

    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.

    0 讨论(0)
  • 2021-01-31 19:48

    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
    
    0 讨论(0)
  • 2021-01-31 19:48

    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.

    0 讨论(0)
提交回复
热议问题