Skip to content

Instantly share code, notes, and snippets.

@mbajur mbajur/.md
Created Apr 29, 2016

Embed
What would you like to do?
How to create small, unique tokens in Ruby

How to create small, unique tokens in Ruby

That is is basically a "fork" of blog article i'm constantly returning to. It seems that the blog is down:

My choice: Dave Bass’s rand().to_s() trick

Dave Bass proposed this which I picked up for my implementation (here for an 8-chars token):

>> rand(36**8).to_s(36)
=> "uur0cj2h"

The result can be used as an url; pretty neat. It relies on the ability of Fixnum to translate itself to a string in a given base (here we use base 36, which I rarely use!). This can be used in an ActiveRecord model for instance:

class Customer < ActiveRecord::Base
  validates_presence_of :access_token
  validates_uniqueness_of :access_token

protected
  def before_validation_on_create
    self.access_token = rand(36**8).to_s(36) if self.new_record? and self.access_token.nil?
  end 
end

Jamie Macey feedback

Jamie proposed several options. First, use a substring of SHA1, which is “small enough to be usable, but still pseudo-random enough for temporary tokens to not be guessable” :

>> require 'digest'
=> []
>> Digest::SHA1.hexdigest("some-random-string")[8..16]
=> "2ebe5597f"

Another technique is to rely on ActiveSupport SecureRandom, and tweak the results a bit to get a url-friendly token. Here’s my final bit of code with this method:

>> require 'active_support'
=> []
>> ActiveSupport::SecureRandom.base64(8).gsub("/","_").gsub(/=+$/,"")
=> "AEWQyovNFo0" 

Jamie’s last proposal is “not terribly robust, but functional” :

>> chars = ['A'..'Z', 'a'..'z', '0'..'9'].map{|r|r.to_a}.flatten
>> Array.new(6).map{chars[rand(chars.size)]}.join
=> "g64wdR"

Ryan Davis: let’s put more words in it

Ryan proposed something totally different:

>> words = File.read("/usr/share/dict/words").split; max = words.size
=> 234936
>> "#{words[rand(max)]}-#{words[rand(max)]}" 
=> "loquat-motorial"

The idea is interesting. You’ll need to ensure your dictionary doesn’t contain insults, if your user base cares about that :)

Another option Ryan got from Eric is to use the quite unknown bubble-babble to make hash values more readable:

>> require 'digest/bubblebabble'
=> true
Digest.bubblebabble(Digest::SHA1::hexdigest("random string")[8..12]) 
=> "xesik-fymak-gunax"

John Mettraux’s Rufus Mnemo

rufus-mnemo has the ability to translate an integer into easy-to-remember words, based on Japanese syllabes:

>> require 'rufus/mnemo'
>> s = Rufus::Mnemo::from_integer rand(8**5)
=> "bisoshi" 

Pretty neat! The generated words are “easy to the latin ears”. Take care of the meaning if your users are Japanese-speaking.

If you use UUID – be careful with Solaris zones!

If you deploy to Solaris zones, be careful about that: some other libraries I had a look at, like the very nice assaf’s uuid, are relying on macaddr, which doesn’t seem to work on Solaris Zone.

Have you got more ?

Please share them in comments.

@danieldraper

This comment has been minimized.

Copy link

danieldraper commented Jul 18, 2018

Another possible option is hashids

@whatcould

This comment has been minimized.

Copy link

whatcould commented Nov 13, 2019

This is helpful! Also discovered you can use SecureRandom.urlsafe_base64 to get a url-safe random hash; you can specify the byte-length of the random number to get a shorter string (eg SecureRandom.urlsafe_base64(5)).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.