Skip to content

How To: Allow users to edit their password

Hashrocket Employees edited this page Dec 17, 2020 · 54 revisions

By default, Devise allows users to change their password using the registerable module.

Here we are going to provide a few solutions on how to allow users to change their password.

Solution 1

Make the following link in your view:

<%= link_to "Change your password", edit_user_registration_path %>

Notice: This'll work if you didn't do any modification in your routes.rb file such as

devise_for :users, :skip => [:registrations]

Notice 2: If you use the latest Devise with Strong Parameters, you should add this line to your ApplicationController.rb:

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

  def configure_permitted_parameters
    update_attrs = [:password, :password_confirmation, :current_password]
    devise_parameter_sanitizer.permit :account_update, keys: update_attrs
  end
end

Solution 2

Let's suppose that you don't want to allow users to sign up but you want to allow to change password for registered users. Just paste this code in routes.rb:

devise_for :users, :skip => [:registrations]                                          
as :user do
  get 'users/edit' => 'devise/registrations#edit', :as => 'edit_user_registration'    
  put 'users' => 'devise/registrations#update', :as => 'user_registration'            
end

And then you can make such link in your view:

= link_to "Change your password", edit_user_registration_path

Notice: you will need to update default devise views accordingly, i.e. in app/views/devise/registrations/edit.html.erb change registration_path(resource_name) to user_registration_path(). If you are using shared views for multiple models, you can use send("#{resource_name}_registration_path").

Notice: If you are using rails 4.0+ you should be using patch instead of put for updates. You should change the method in the form_tag residing in app/views/devise/registrations/edit.html.erb and the routes.rb file.

Solution 3

But sometimes, developers want to provide their custom actions that change the password. In such cases, the best option is for you to manually create a controller:

class UsersController < ApplicationController
  
  before_action :authenticate_user!

  def edit
    @user = current_user
  end

  def update_password
    @user = current_user
    if @user.update(user_params)
      # Sign in the user by passing validation in case their password changed
      bypass_sign_in(@user)
      redirect_to root_path
    else
      render "edit"
    end
  end

  private

  def user_params
    # NOTE: Using `strong_parameters` gem
    params.require(:user).permit(:password, :password_confirmation)
  end
end

If you are using several scopes, specify the one you are signing in:

bypass_sign_in @user, scope: :user

The route should be the following:

resource :user, only: [:edit] do
  collection do
    patch 'update_password'
  end
end

And then proceed to implement the view, as below:

<%= form_for(@user, :url => { :action => "update_password" } ) do |f| %>
  <div class="field">
    <%= f.label :password, "Password" %><br />
    <%= f.password_field :password, :autocomplete => "off"  %>
  </div>
  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation %>
  </div>
  <div class="action_container">
    <%= f.submit %>
  </div>
<% end %>

To use "confirm_password" field to force user to enter old password before updating with the new one: Change @user.update(user_params) to @user.update_with_password(user_params) in the controller along with adding :current_password to the permitted parameters, then and add the following to the view code:

  <div class="field">
    <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
    <%= f.password_field :current_password %>
  </div>

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.

Clone this wiki locally