Skip to content

How To: Allow users to edit their account without providing a password

Stuart Olivera edited this page Jul 21, 2014 · 72 revisions

By default, Devise allows users to change their password using the :registerable module. But sometimes, developers want to create other actions that allow the user to change their information without requiring a password. In such cases, the best option is for you to manually override the Devise registrations controller:

class RegistrationsController < Devise::RegistrationsController
  def update
    # For Rails 4
    account_update_params = devise_parameter_sanitizer.sanitize(:account_update)
    # For Rails 3
    # account_update_params = params[:user]

    # required for settings form to submit when password is left blank
    if account_update_params[:password].blank?
      account_update_params.delete("password")
      account_update_params.delete("password_confirmation")
    end

    @user = User.find(current_user.id)
    if @user.update_attributes(account_update_params)
      set_flash_message :notice, :updated
      # Sign in the user bypassing validation in case their password changed
      sign_in @user, :bypass => true
      redirect_to after_update_path_for(@user)
    else
      render "edit"
    end
  end
end

In the example above, the user will be able to change any information they want. Including the password, e-mail etc. If you want users to update all information except the password itself, you can use update_without_password provided by Devise and then proceed to implement the views. For example:

class RegistrationsController < Devise::RegistrationsController
  def update
    @user = User.find(current_user.id)

    successfully_updated = if needs_password?(@user, params)
      @user.update_with_password(devise_parameter_sanitizer.sanitize(:account_update))
    else
      # remove the virtual current_password attribute
      # update_without_password doesn't know how to ignore it
      params[:user].delete(:current_password)
      @user.update_without_password(devise_parameter_sanitizer.sanitize(:account_update))
    end

    if successfully_updated
      set_flash_message :notice, :updated
      # Sign in the user bypassing validation in case their password changed
      sign_in @user, :bypass => true
      redirect_to after_update_path_for(@user)
    else
      render "edit"
    end
  end

  private

  # check if we need password to update user data
  # ie if password or email was changed
  # extend this as needed
  def needs_password?(user, params)
    user.email != params[:user][:email] ||
      params[:user][:password].present? ||
      params[:user][:password_confirmation].present?
  end
end

The above example takes care of validating the presence of users' current password if changing email or password. Otherwise it lets the request process without requiring current password.

Note that you need to tell Devise to route registrations to your new controller in config/routes.rb:

  devise_for :users, :controllers => { :registrations => "registrations" }

Remember, Devise models are like any model in your application. If you want to provide custom behavior, just implement new actions and new controllers. Don't try to bend Devise.

Strong Parameters with Rails 4

With the introduction of Strong Parameters, you will need to specify permitted parameters in your overridden controllers. For example:

class ApplicationController < ActionController::Base
  before_filter :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) do |u|
      u.permit(:first_name, :last_name, :username, :email, :password, :password_confirmation)
    end
    devise_parameter_sanitizer.for(:account_update) do |u|
      u.permit(:first_name, :last_name, :username, :email, :password, :password_confirmation)
    end
  end
end

And then change update calls using the appropriate method below:

@user.update_with_password(devise_parameter_sanitizer.sanitize(:account_update))

@user.update_without_password(devise_parameter_sanitizer.sanitize(:account_update))

Refer to Devise README for lazy and non-lazy ways implementations of accomplishing this.

Using with Apotomo

Add the following line in your Apotomo widget class:

include Devise::Controllers::Helpers

Using a custom Registrations controller with Devise views

By default devise views uses methods such as resource and resource_name. If creating a new Registrations controller as recommended above for use with the default views generated by Devise, you may find it helpful to re-define these methods in a RegistrationsHelper:

module RegistrationsHelper
  def resource_name
    :user
  end

  def resource
    @resource ||= User.new
  end

  def devise_mapping
    @devise_mapping ||= Devise.mappings[:user]
  end
end

Getting the reconfirmable option to work

As described here the update action detailed above breaks the reconfirmable functionality if you want a user to reconfirm their new email when updating it.

To get this to work you need to import as much of the original controller update action as possible (this is taken from the most recent release - 3.2.4)

class RegistrationsController < Devise::RegistrationsController

  def update
    # required for settings form to submit when password is left blank
    if account_update_params[:password].blank?
      [:password,:password_confirmation,:current_password].collect{|p| account_update_params.delete(p) }
    end
    if account_update_params[:password].present? and account_update_params[:current_password].blank?
      [:current_password].collect{|p| account_update_params.delete(p) }
    end

    self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
    prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)

    if update_resource(resource, account_update_params)
      yield resource if block_given?
      if is_flashing_format?
        flash_key = update_needs_confirmation?(resource, prev_unconfirmed_email) ?
          :update_needs_confirmation : :updated
        set_flash_message :notice, flash_key
      end
      sign_in resource_name, resource, bypass: true
      respond_with resource, location: after_update_path_for(resource)
    else
      clean_up_passwords resource
      respond_with resource
    end
  end

  protected

  def update_resource(resource, params)
    resource.update_attributes(params)
  end
end

As you can see you also need to override the update_resource(resource, params) to update the record without requiring its password.

Clone this wiki locally