devise: 'Confirmation token is invalid' when confirming after registering
When registering User who utilizes :confirmable, when the user clicks on the e-mail confirmation link, they receive an “Invalid confirmation token” error message.
This issue is present using:
- Devise 3.1.0 (
041fcf90807df5efded5fdcd53ced80544e7430f) - Rails 4.0.0
Code sample
user.rb
class User
include Mongoid::Document
include Mongoid::Attributes::Dynamic
# Attributes
# ----------
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable
...
## Confirmable
field :confirmed_at, type: DateTime
field :confirmation_token, type: String
field :confirmation_sent_at, type: DateTime
field :unconfirmed_email, type: String
Possible explanation
After registering, but before confirming, the User.confirmation_token is equal to 778fb (truncated for readability.) This token exactly matches the confirmation email’s link (i.e. ?confirmation_token=778fb.)
However, after reading this post it appears that these tokens shouldn’t match, because Devise uses the email token to generate the matching User.confirmation_token with which to confirm them by.
Digging deeper into confirmable.rb…
The code that generates the User.confirmation_token:
# Generates a new random token for confirmation, and stores
# the time this token is being generated
def generate_confirmation_token
raw, enc = Devise.token_generator.generate(self.class, :confirmation_token)
@raw_confirmation_token = raw
self.confirmation_token = enc
self.confirmation_sent_at = Time.now.utc
end
The code that confirms a user:
# Find a user by its confirmation token and try to confirm it.
# If no user is found, returns a new user with an error.
# If the user is already confirmed, create an error for the user
# Options must have the confirmation_token
def confirm_by_token(confirmation_token)
original_token = confirmation_token
confirmation_token = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
confirmable.confirm! if confirmable.persisted?
confirmable.confirmation_token = original_token
confirmable
end
From this code, it appears that the token, say 778fb, is persisted to User.confirmation_token, then sent out in the confirmation e-mail. When the user clicks the confirmation link, 778fb is passed into confirm_by_token, which generates another token (i.e. 945ac) which it searches the User table for finding nothing, thus invalid token.
It seems like in this case, User.confirmation_token should actually store 945ac but still send out 778fb in the confirmation link. I ran a simple test through my console, which seemed to confirm (no pun intended) this hypothesis:
new_token = Devise.token_generator.digest(User, :confirmation_token, '778fb')
u = User.first # The already registered, but unconfirmed user
u.confirmation_token = new_token
u.save
User.confirm_by_token('778fb') # Succeeds
Last thoughts…
Can someone explain why this is happening? It seems a little too obvious of a ‘bug’ to be true, but I haven’t been able to work around it any other way.
See the following related links:
About this issue
- Original URL
- State: closed
- Created 11 years ago
- Comments: 26 (5 by maintainers)
Ahhh ok, that’s definitely it. Thanks!
For anyone else who stumbles into this, you need to change your
views/<user>/mailer/confirmation_instructions.html.erbto look something like this:@josevalim I just figured it out. I had the following in an
after_createcallback.That
self.savewas updating theconfirmation_token, because it was happening mid transaction. If I move it into anafter_commitcallback, or change it toself.account = Account.create, it works fine.Interesting little edge case there.
Sorry for all of the messages. 😃
I’ve gone through a plethora of answers that mention the @token to have the confirmations module work. It still doesn’t. Even with a new application, @token doesn’t work.
FWIW I’m using the confirmation token in a scenario where I give it out to a third-party email platform where it gets inserted into a confirmation email template. For that reason I have to extract it while in
RegistrationsController#createusing the hacky approach ofinstance_variable_get(:@raw_confirmation_token). Would be nice to have a proper accessor for that purpose.Ah ok I figured it out. It’s ugly as sin, but: