32 thoughts on “Registrazione in due step con Devise

  1. kris

    Passing the user id as a hidden field is a bad idea. It would be trivial to change that, submit the form and get access to another account.

  2. Claudio Post author

    Hello Kris, thanks for reporting. In fact should be used the the cofirmation_token in place of the id.

    I’ve fixed the example code.

  3. Kevin Brown

    Claudio, I’ve been working on this for about two weeks and I’m just stuck. Everything I have looks exactly like you have showed except my model is called ‘user’. I’m getting a `no route matches ‘confirm_account’` message. I tried to change ‘put’ to ‘match’ in my routes.rb, but that threw a lot more errors (at least it was finding it, though…maybe?)

    I’d really like to get this working. Do you have any advice?

  4. Claudio Post author

    Hi Kevin, I don’t know what cause the error.
    Have you try to see the routes with rake routes?
    If you want post your code so I can try to find the problem.

  5. Fred Schoeneman

    Thanks for this! I had just decided that asking for a password at signup, and then requiring confirmation, makes more sense for my project. I will let you know how it works out!

  6. Fred Schoeneman

    Claudio,

    I think there’s a missing “if” statement in code you provide in the account model for “def password_match?”

    1
    2
    3
    4
    5
    def password_match?
        self.errors[:password] << 'password not match'  password != password_confirmation
        self.errors[:password] << 'you must provide a password' if password.blank?
        password == password_confirmation and !password.blank?
      end

    this works for me:

    1
    2
    3
    4
    5
    def password_match?
        self.errors[:password] << 'password not match' if password != password_confirmation
        self.errors[:password] << 'you must provide a password' if password.blank?
        password == password_confirmation and !password.blank?
      end

    Thanks again for this blog post. You really helped me out.

  7. Naveed

    Hi

    I used your method in my rails 3 app with webrick and nginx but its taking too much time for user registration and user confirmations although i used the same code you provided.

    Regards

  8. Greg

    I believe you also need to change Account.find to Account.find_by_confirmation_token in the confirm_account action of the controller.

  9. Nick

    I appreciate you taking the time to write this up. It is very close to what I need for my site and it is helping me tremendously. I followed your instruction and I ran into an error. (I am sure I just did something wrong but I was hoping you might be able to help if you have time). It works all the way up to the entering the password part. Once I submit the password it gives and error.

    ActiveRecord::RecordNotFound in ConfirmationsController#confirm_admin
    Couldn’t find Admin with id=WjqDa7hz2Y9bUh5EKxBf
    Rails.root: Websites/Development/Rails/appignani
    app/controllers/confirmations_controller.rb:10:in `confirm_admin’

    It should not be looking for ID and as far as the code I can see and entered it is not. Here is the confirm_admin code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     def confirm_admin
        @admin = Admin.find(params[:admin][:confirmation_token])
        if @admin.update_attributes(params[:admin]) and @admin.password_match?
          @admin = Admin.confirm_by_token(@admin.confirmation_token)
          set_flash_message :notice, :confirmed      
          sign_in_and_redirect("admin", @admin)
        else
          render :action => "show"
        end
      end

    I appreciate any help you can give on this. Thank you.

  10. Nick

    I figured it out. It is what the last post before mine suggested.

  11. Shane

    I was looking to implement something similar when I ran across this post. I was considering using the built-in token_authenticatable module in Devise to provide access to a newly created account where the user has not set a password. I was going to set the password to something random and force a change when the user confirms the account. I was going to allow access to confirmation and password changing via the token_authentication. I am glad I ran across this because I don’t think I need to use the token_authenticatable module, I can just use the confirmation token! I think I will still just set the password to something random for a non-confirmed user since I don’t want to override the password validation methods in Devise as I have other models that need it.

    Anyways, thanks for the write, lots of good ideas in here!

  12. Shane

    Works great!

    Step #4 under confirm_account method, Account.find should be Account.find_by_confirmation_token

  13. Shane

    For my case I found that I could simply override password_required in my user model like so:

    1
    2
    3
      def password_required?
        confirmed?
      end

    This allows the user to confirm their account without setting a password only when their account has not yet been confirmed. After confirmation, the password is required.

  14. Kevin Triplett

    Excellent post, thanks! I have multiple Devise resources and I want each resources to be able to use this feature. So here’s what I did…

    In my confirmation controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class ConfirmationsController < Devise::ConfirmationsController
      def show
        self.resource = resource_class.find_by_confirmation_token(params[:confirmation_token])
        if !resource.present?
          render_with_scope :new
        end
      end

      def confirm
        self.resource = resource_class.find_by_confirmation_token(params[resource_name][:confirmation_token])
        if resource.present? && resource.update_attributes(params[resource_name]) && resource.password_match?
          resource = resource_class.confirm_by_token(resource.confirmation_token)
          set_flash_message :notice, :confirmed
          sign_in_and_redirect(resource_name.to_s, resource)
        else
          render :action => "show"
        end
      end
    end

    Very subtle difference, but now any Devise resource can use this controller. (I added the check for present? in case someone tries to submit the form a second time.) Oh — you also have to change the routing configuration (which note that the new version of Devise now deprecates passing a block to #device_for:

    1
    2
    3
    4
    5
    6
    7
    8
    9
      devise_for :clients, :controllers => {:confirmations => 'confirmations'}
      devise_for :admins,  :controllers => {:confirmations => 'confirmations'}

      devise_scope :clients do
        put "confirm", :to => "confirmations#confirm"
      end
      devise_scope :admins do
        put "confirm", :to => "confirmations#confirm"
      end

    Thanks again, Claudio — and if you have time, ha ha, please change above your Account.find to Account.find_by_confirmation_token above. Just in case someone is too lazy and they don’t read these comments. Again, thanks for documenting this sweet feature!

    Also, thanks to Shane, I made my model methods and grammatized the error messages:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
      def password_required?
        super if confirmed?
      end

      def password_match?
        if password_confirmation.blank?
          self.errors[:password_confirmation] << "cannot be blank"
        elsif password != password_confirmation
          self.errors[:password_confirmation] << "does not match password"
        end
        if password.blank?
          self.errors[:password] << "cannot be blank"
        end
        password == password_confirmation && !password.blank?
      end
  15. Kevin Triplett

    Serves me right for posting late at night! It worked for my clients model but devise gets confused with the same routing information for multiple resources. So here’s what works with multiple Devise resources:

    In my routes.rb:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
      devise_for :clients, :controllers => {:sessions => 'sessions', :confirmations => 'confirmations'}
      devise_for :admins, :controllers => {:sessions => 'sessions', :confirmations => 'confirmations'}
      devise_for :super_users, :controllers => {:sessions => 'sessions', :confirmations => 'confirmations'}

      devise_scope :client do
        match "/client/confirm" => "confirmations#confirm", :as => :client_confirm
      end
      devise_scope :admin do
        match "/admin/confirm" => "confirmations#confirm", :as => :admin_confirm
      end
      devise_scope :super_user do
        match "/super_user/confirm" => "confirmations#confirm", :as => :super_user_confirm
      end

    (Notice the singular v. plural of the resource names. I’m sure put v. match would also work.)

    And here’s the hack I had to do in order to use the same view for each resource (I use the semantic forms gem):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    - confirm_path = send("#{resource_name}_confirm_path")
    %h2 You're almost done! Now create a password to securely access your account.
    = semantic_form_for(resource, :as => resource_name, :url => confirm_path) do |form|
      = devise_error_messages!
      = form.inputs do
        = form.input :email, :input_html => { :disabled => true }
        = form.input :password
        = form.input :password_confirmation
        = form.input :confirmation_token, :as => :hidden
      = form.actions do
        = form.action :submit, :label => '
    Confirm Account'

    I disabled the email field so the client can see their email address but not change it. Sorry for the mis-post! Carry on.

  16. Kevin Triplett

    One more thing: in my confirmations controller, I had to reference the token this way:


    resource_class.confirm_by_token(params[resource_name][:confirmation_token])

    Otherwise, it raised an exception for me. YMMV

  17. Kevin Triplett

    Me again, but with sleep I noticed that if the user changes their email address then they are sent a confirmation email and, after clicking the confirmation link, the user is presented with the same confirmation screen asking them to enter in their new password. Whoops. The client is not expecting this behavior so here is how I modified my controller code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class ConfirmationsController < Devise::ConfirmationsController
      before_filter :top_nav_for_public

      def show
        self.resource = resource_class.find_by_confirmation_token(params[:confirmation_token])
        debugger
        if resource.present? && resource.confirmed?
          self.resource = resource_class.confirm_by_token(params[:confirmation_token])
          set_flash_message :notice, :confirmed
          sign_in_and_redirect(resource_name, resource)
        else
          render_with_scope :new
        end
      end

      def confirm
        debugger
        self.resource = resource_class.find_by_confirmation_token(params[resource_name][:confirmation_token])
        if resource.present? && resource.update_attributes(params[resource_name]) && resource.password_match?
          self.resource = resource_class.confirm_by_token(params[resource_name][:confirmation_token])
          set_flash_message :notice, :confirmed
          sign_in_and_redirect(resource_name.to_s, resource)
        else
          render :action => "show"
        end
      end
    end

    I also added self.resource= statements, which I forgot to do in the earlier code. Again, great blog post Claudio!

  18. Kevin Triplett

    Whoops — sorry, take out the two debugger statements and you have the correct code.

  19. Kevin Triplett

    Dang, so Devise deprecated and then removed #render_with_scope, but that’s okay, it made #show simpler:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class ConfirmationsController < Devise::ConfirmationsController
      before_filter :top_nav_for_public

      def show
        self.resource = resource_class.find_by_confirmation_token(params[:confirmation_token])
        if resource.present? && resource.confirmed?
          self.resource = resource_class.confirm_by_token(params[:confirmation_token])
          set_flash_message :notice, :confirmed
          sign_in_and_redirect(resource_name, resource)
        end
      end

      def confirm
        self.resource = resource_class.find_by_confirmation_token(params[resource_name][:confirmation_token])
        if resource.present? && resource.update_attributes(params[resource_name]) && resource.password_match?
          self.resource = resource_class.confirm_by_token(params[resource_name][:confirmation_token])
          set_flash_message :notice, :confirmed
          sign_in_and_redirect(resource_name, resource)
        else
          render :action => "show"
        end
      end
    end

    Much nicer!

  20. Claudio Post author

    Thank you Kevin for your big contribution! I’ve updated the post with a reference to the Devise wiki page.

  21. John Mathis

    Claudio, thanks for the article. Kevin, thanks for the Wiki page. The URL is access-controlled, though available in Google cache. Could you please open up the Wiki page.

  22. Kevin Triplett

    Sorry, I meant Claudio (I have to stop playing my Nintendo)

  23. Adam

    After the form on the confirmations show page is submitted, how can I customize the redirect for that form?

  24. Kaworu

    find_by_confirmation_token(nil).update_attributes is dangerous

Comments are closed.