Pete Yandell
2005-Jul-26 05:31 UTC
Generating unique random tokens for ActiveRecord objects
I have an ActiveRecord subclass that needs to generate a random (hard to guess) token for each record in its corresponding table. Currently I''m doing something like this: def before_create self[''token''] = random_value while self.class.find_by_token(self[''token'']) self[''token''] = random_value end end (random_value is a method that generates a random value for the token, not surprisingly.) I can see a potential timing error there if a row gets written between when I check for uniqueness of the new value and when I save the new row. What I''d like to do is declare a unique index in the database, have my model object catch a failed save, check if it failed due to the token not being unique, regenerate the token, and retry the save. Is there any neat way to do this in ActiveRecord? (How does validates_uniqueness do it? Does it suffer from the same timing problem?) Cheers, Pete Yandell _______________________________________________ Rails mailing list Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails
Michael Koziarski
2005-Jul-26 06:04 UTC
Re: Generating unique random tokens for ActiveRecord objects
On 7/26/05, Pete Yandell <pete-AylieETRJAdBDgjK7y7TUQ@public.gmane.org> wrote:> I have an ActiveRecord subclass that needs to generate a random (hard to > guess) token for each record in its corresponding table. Currently I''m doing > something like this: > > def before_create > self[''token''] = random_value > > while self.class.find_by_token(self[''token'']) > self[''token''] = random_value > end > end > > (random_value is a method that generates a random value for the token, not > surprisingly.) > > I can see a potential timing error there if a row gets written between when > I check for uniqueness of the new value and when I save the new row. > > What I''d like to do is declare a unique index in the database, have my model > object catch a failed save, check if it failed due to the token not being > unique, regenerate the token, and retry the save. Is there any neat way to > do this in ActiveRecord? (How does validates_uniqueness do it? Does it > suffer from the same timing problem?)Just create the unique index, then for your token value, use something like SHA1 of secret + current time + any other bits and pieces. The probability of a clash is so small you can just ignore it and handle it like any other unexpected exception> > Cheers, > > Pete Yandell > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails > > >-- Cheers Koz
Pete Yandell
2005-Jul-26 10:08 UTC
Re: Generating unique random tokens for ActiveRecord objects
On 26/07/2005, at 4:04 PM, Michael Koziarski wrote:> On 7/26/05, Pete Yandell <pete-AylieETRJAdBDgjK7y7TUQ@public.gmane.org> wrote: > >> What I''d like to do is declare a unique index in the database, >> have my model >> object catch a failed save, check if it failed due to the token >> not being >> unique, regenerate the token, and retry the save. Is there any >> neat way to >> do this in ActiveRecord? (How does validates_uniqueness do it? >> Does it >> suffer from the same timing problem?) > > Just create the unique index, then for your token value, use > something like SHA1 of secret + current time + any other bits and > pieces. The probability of a clash is so small you can just ignore > it and handle it like any other unexpected exceptionTwo problems: First, I want to keep the tokens short. They have to go into URLs. SHA1 is too long. Second, the idea of playing the odds like that makes me nervous. I''d rather build something that''s guaranteed robust than deal with that one user who might run into the problem. (For SHA1 it probably would be OK, but it may not be for fewer bits.) Surely it shouldn''t be that hard? Pete Yandell
Michael Koziarski
2005-Jul-26 10:21 UTC
Re: Generating unique random tokens for ActiveRecord objects
> Two problems: > > First, I want to keep the tokens short. They have to go into URLs. > SHA1 is too long.irb(main):007:0> Digest::SHA1.hexdigest("asdf") => "3da541559918a808c2402bba5012f6c60b27661c" It''s really not that long (40). MD5''s a bit shorter (32) irb(main):009:0> Digest::MD5.hexdigest("asdf") => "912ec803b2ce49e4a541068d495ab570"> Second, the idea of playing the odds like that makes me nervous. I''d > rather build something that''s guaranteed robust than deal with that > one user who might run into the problem. (For SHA1 it probably would > be OK, but it may not be for fewer bits.) Surely it shouldn''t be that > hard?What you''re looking for is: * Unique * Secure What you''re describing is a cryptographic hashing function, making up your own is an option, but much, much riskier. As for ''playing the odds'', the chances of a collision are statistically insignificant. But if you''re worried, you could rescue any exceptions from save, check if it''s about the unique index and try to regenerate a token. But bear in mind, rails'' session IDs use a similar approach, so the chances of a session ID are the same as the hash collision above. -- Cheers Koz