I’m using the generates_token_for method in my Rails application for email verification. I’ve seen two different implementations and I’m not sure which one is correct or if there’s any difference in security:
Version 1 (with SecureRandom):
generates_token_for :email_verification, expires_in: 2.hours do
SecureRandom.hex(20)
end
My question is:
Is it necessary to provide a custom block(something like: SecureRandom.hex(20)), or does generates_token_for already generate secure and unpredictable tokens by default?
A block may also be specified. When generating a token with TokenFor#generate_token_for, the block will be evaluated in the context of the record, and its return value will be embedded in the token as JSON. Later, when fetching the record with find_by_token_for, the block will be evaluated again in the context of the fetched record. If the two JSON values do not match, the token will be treated as invalid. Note that the value returned by the block should not contain sensitive information because it will be embedded in the token as human-readable plaintext JSON.
Purpose for this is to automatically invalidate a token for a record in certain conditions. For example you could provide a hash of the current encrypted password and email so a password reset token will be automatically invalidated if the password changes or the email changes
I am answering my question. After reading the source code for the feature and some PRs related to it on GitHub, I believe Version 1 (above) should never be used and it is completely wrong, since the block will keep generating new random strings and the token will always be invalid. The correct implementation is something along the lines of:
generates_token_for :email_verification, expires_in: 2.hours do
email
end
This ensures that the token is for a specific email, and the email didn’t change between the token generation and validation.
The documentation literally says not to embed sensitive info
Note that the value returned by the block should not contain sensitive information because it will be embedded in the token as human-readable plaintext JSON .
I am not sure why you think SecureRandom.hex(20) is sensitive info. It is a randomly generated string, but using it seems wrong anyway. The documentation uses the last N characters of password salt as an example to store data that can be evaluated against the record context to validate the token.