Follow this guide, then we need change some little things.
Create a User.find_by_public_confirmation_token
method.
class User < ActiveRecord::Base
devise :database_authenticatable, :confirmable, ...
...
def self.find_by_public_confirmation_token(public_confirmation_token)
confirmation_token = Devise.token_generator.digest(self, :confirmation_token, public_confirmation_token)
find_by_confirmation_token(confirmation_token)
end
end
Then use this new method instead of User.find_by_confirmation_token
.
class ConfirmationsController < Devise::ConfirmationsController
def show
...
self.resource = resource_class.find_by_public_confirmation_token(params[:confirmation_token]) if params[:confirmation_token].present?
...
end
def confirm
...
self.resource = resource_class.find_by_public_confirmation_token(params[:confirmation_token]) if params[:confirmation_token].present?
...
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
...
end
end
Then change the hidden field to pass the confirmation token in app/views/confirmations/show.html.erb
.
<%= hidden_field_tag :confirmation_token, params[:confirmation_token] %>
Devise uses two confirmation tokens. A public one that is sent to your user and a second confirmation token that is stored in the database and used in the confirm_by_token
method.
The trouble starts when you pass the public confirmation token to devise methods like find_by_confirmation_token
that expect the private confirmation token.
In my case the email template was in app/views/devise/mailer/confirmation_instructions.html.erb
:
<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
When you set a breakpoint in that mailer method, you'll see those two tokens in action.
[10] pry(#<#<Class:0x007fbb7f5690e0>>)> @resource.confirmation_token
=> "d1115ad6d5f8e59fd6416f54d2651141294ae93d394f5dc1e958cafa19625132"
[11] pry(#<#<Class:0x007fbb7f5690e0>>)> @token
=> "RUx9NoTws175xHR6xK47"
TODO: Find out where @token is set, how it is generated from the confirmation_token
Looking at the confirm_by_token
method in /devise/models/confirmable.rb [TODO: Link to gh] shows you how the two tokens are connected.
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
With Devise.token_generator.digest
you can convert the public confirmation token to the internal confirmation token.
The User.find_by_public_confirmation_token
method that I defined earlier is a simple hack that translates the public confirmation token into a private confirmation token using the token_generator
and then uses find_by_confirmation_token
that comes with devise.