I have a Rails 4 app with Devise 3.2.2 using :confirmable
, and am running into an issue with it sending invalid confirmation tokens, but only the first time. After you resend the confirmation token, the link works.
The relevant routes:
devise_for :users, skip: [:sessions, :passwords, :confirmations, :unlocks, :registrations, :invitations]
as :user do
...
# joining
get '/register' => 'devise/registrations#new', as: 'new_user_registration'
post '/register' => 'devise/registrations#create', as: 'user_registration'
...
end
...and the mail template in question:
<p>Welcome <%= @email %>!</p>
<p>You can confirm your account email through the link below:</p>
<p><%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @token) %></p>
As far as I can tell, everything is pretty standard fare, and the fact that it only fails when initially creating and not on the resend is rather confusing.
--Update--
When clicking on the link, I get the following in my dev log:
Started GET "/account/confirm?confirmation_token=3bQP-EvYJPv74s9AMz63" for 127.0.0.1 at 2014-02-07 12:26:10 -0500
Processing by Users::ConfirmationsController#show as HTML
Parameters: {"confirmation_token"=>"3bQP-EvYJPv74s9AMz63"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."confirmation_token" = 'e191b48746f15014beb32073f08de3c7aa13a2282216f089fa71d083901b3dca' ORDER BY "users"."id" ASC LIMIT 1
Rendered devise/shared/_links.slim (1.2ms)
Rendered devise/confirmations/new.html.slim within layouts/devise (7.0ms)
Rendered layouts/_jquery.slim (1.0ms)
Completed 200 OK in 32ms (Views: 23.4ms | ActiveRecord: 0.4ms)
The referenced controller is:
class Users::ConfirmationsController < Devise::ConfirmationsController
protected
def after_confirmation_path_for(resource_name, resource)
if signed_in?
signed_in_root_path(resource)
else
new_session_path(resource_name, email: resource.email)
end
end
end
-- Update 2 --
The following is a gist of the logs from the entire signup process: https://gist.github.com/jbender/bbe079c2dd3fa2d1e664
It looks like there's something which is causing your user to be immediately updated and resaved (including generation of a new confirmation token) before the registration create
action has finished. Looking at your gist:
The question is why did the user get saved twice? It looks like you've added a tos_accepted_at attribute. Is there some code somewhere (an after filter?) resaving the user including this attribute, along with all the others, which then triggers devise's confirmable logic again? Speaking of which, I'm most intrigued that it didn't immediately cause a second email to be sent (with a confirmation token that works), given that the confirmation_sent_at timestamp changes.
I notice that there's some version tracking going on too, judging by other INSERTs, though it doesn't look like that can be interfering.
As an additional sanity check, if you could bludgeon the rails console into encrypting what you expect the confirmation token to be, you'd find that the encrypted version matches the version at line 6 of your gist, which is then overwritten in the db at line 9. I can't try this, because it depends on your rails secret (see Devise::TokenGenerator in devise/toekn_generator.rb). Anyway, not necessary, but would confirm that the original confirmation token is never going to work.
I assume the resend works because it's just the normal case (no double save, no extra tos_accepted_at field etc.)
UPDATE
As discussed in the comments, the problem was indeed the tos_accepted_at attribute. It was being updated using update_attribute()
in an after_create
callback. For reasons which are a bit unclear, this seemed to be dirtying all attributes, so they were all saved as well (update_attribute
also saves all other attributes if they're dirty) and devise generated a new confirmation token as part of this process (though we don't think it should have as the email address hadn't actually changed!). Changing your filter which saved the updated tos_accepted_at
to a before_save
filter instead of after_create
avoided the problem by making sure the user was only saved once, because before_save
obviously happens before that first save as opposed to after_create
which of course happens after the save which inserts the user into the db.