How to Change or Reset your Password with RESTful_authentication

I have used the acts_as_authenticated plugin for quite some time. It works well, and there’s lots of documentation. I’ve been happy with it. Mostly. I say mostly because it has always bothered me that acts_as_authenticated generates unRESTful code, which is tainting my perfectly RESTful application.

So, I recently evaluated the restful_authentication plugin. The plugin works great, but it is lacking the same breadth of documentation. I needed to add the ability handle a lost password and to change a password. Here’s what I did:

1. Create a new Passwords controller.

The new/create actions equate to a lost password (user wants to get a new password). The edit/update actions equate to changing a password (user wants to update their password).

When a user forgets their password and requests a new one, both their login and email will be required. If this login and email pair exists, then a new, random password will be generated and emailed.

When a user desires a new password, they are required to enter their old password in addition to typing and confirming a new password. The old password is required for security sake.

class PasswordsController < ApplicationController
  before_filter :login_from_cookie
  before_filter :login_required, :except => [:create]

  # Don't write passwords as plain text to the log files
  filter_parameter_logging :old_password, :password, :password_confirmation

  # GETs should be safe
  verify :method => :post, :only => [:create], :redirect_to => { :controller => :site }
  verify :method => :put, :only => [:update], :redirect_to => { :controller => :site }

  # POST /passwords
  # Forgot password
  def create
    respond_to do |format|

      if user = User.find_by_email_and_login(params[:email], params[:login])
        @new_password = random_password
        user.password = user.password_confirmation = @new_password
        user.save_without_validation
        UserNotifier.deliver_new_password(user, @new_password)

        format.html {
          flash[:notice] = "We sent a new password to #{params[:email]}"
          redirect_to signin_path
        }
      else
        flash[:notice] =  "We can't find that account.  Try again."
        format.html { render :action => "new" }
      end
    end
  end

  # GET /users/1/password/edit
  # Changing password
  def edit
    @user = current_user
  end

  # PUT /users/1/password
  # Changing password
  def update
    @user = current_user

    old_password = params[:old_password]

    @user.attributes = params[:user]

    respond_to do |format|
      if @user.authenticated?(old_password) && @user.save
        format.html { redirect_to user_path(@user) }
      else
        format.html { render :action => 'edit' }
      end
    end
  end

  protected

  def random_password( len = 20 )
    chars = (("a".."z").to_a + ("1".."9").to_a )- %w(i o 0 1 l 0)
    newpass = Array.new(len, '').collect{chars[rand(chars.size)]}.join
  end

end

2. Here is the change password view (edit.html.erb):

<div class="form_container">
    <%= error_messages_for :user %>

    <% form_for(:user, :url => user_password_path(@user), :html => { :method => :put }) do |f| %>
      <fieldset>
<div class="form_row">
          <label for="password">Old password</label>
<%= password_field_tag :old_password %></div>
<div class="form_row">
          <label for="password">New password</label>
          <%= f.password_field :password %></div>
<div class="form_row">
          <label for="password_confirmation">Retype the new password</label>
          <%= f.password_field :password_confirmation %></div>
<div class="submit_row">
          <%= f.submit "Update", :class => "submit" %> or <%= link_to 'Cancel', home_path %></div>
</fieldset>
    <% end %></div>

3. Here is the new password view (new.html.erb):

<div class="form_container">
    <% form_for(:password, :url => passwords_path) do |f| %>
      <fieldset>
<div class="form_row">
          <label for="login">Username</label>
          <%= text_field_tag 'login' %></div>
<div class="form_row">
          <label for="email">Email</label>
          <%= text_field_tag 'email' %></div>
<div class="submit_row">
          <%= submit_tag 'Send Password', :class => "button-to" %></div>
</fieldset>
    <% end %></div>

4. Here’s the user notifier (emailer) code:

You’ll need a user_notifier.rb file and a view template (app/views/user_notifier/new_password.html.erb) which will just contain the text of the email you want to send.

app/models/user_notifier.rb:

class UserNotifier < ActionMailer::Base
  def new_password(user, new_password)
    setup_email(user)
    @subject    += 'Your new password'
    @body[:new_password]  = new_password
  end

  protected

  def setup_email(user)
    @recipients  = "#{user.email}"
    @from        = "Support" "<support@yoursite.com>"
    @subject     = ""
    @sent_on     = Time.now
    @body[:user] = user
  end
end

app/views/user_notifier/new_password.html.erb:

We’re sorry to hear your lost your password. But, there’s no need to worry, because we’ve created a new, temporary password for you.

Your new password is: <%=h @new_password %>

You can change this password to something more memorable once you log into your account.

5. Here’s the modified routes.rb file:

  map.resources :passwords
  map.resources :users, :has_one => [:password]

Tags:

Monday, March 24th, 2008 Programming

21 Comments to How to Change or Reset your Password with RESTful_authentication

  • jason says:

    the code look so compelling that i am actually trying to integrate it into my project.
    however,
    i can’t piece the things together.for example

    i.e. if the user has forgotten their password. what link (link_to) do i give them from where? i.e signup.

    it would be great if you could provide a little more such information so that it is more understandable.

    also i don’t understand – map.connect ”, :controller => ‘site’
    what is site in this case? i.e i dont have controller site.

    Thanks for the help—–

  • Justin says:

    If a user is logged in and they want to change their password, link to edit_user_path(current_user). If a user is not logged in and has forgotten their password, link to the forgot password view by using new_password_path().

    The route to the site controller has nothing to do with the password code. It was a copy/paste error and I’ve removed it from this article.

    Thanks for the feedback!

  • jason says:

    Thanks for the reply.
    I have have plugged in the code as above and I am getting a

    uninitialized constant PasswordsController::UserNotifier

    at the line: UserNotifier.deliver_new_password(user…….

    Excuse the nuby but what am i missing?

    thanks for the help.

  • jason says:

    Justin,
    so i got it working….it well coded and there are lots of new expressions that i will be using from now on – thanks.
    On another note, there really is a ton of code missing from your example (an a few errors) which makes it very difficult if not impossible for us newbies to get to grips with it. On the upside – i learned a lot.

    Still maybe you could find the time add the missing code for others – i will be documenting what i did on my blog for safe keeping.

    thanks again for a great snippet.

  • Yves says:

    Hey,

    better use:

    find_in_state :first, :active, :conditions => ['LOWER(login) = :login && LOWER(email) = :login', {:login => login.downcase, :email => email.downcase}]

  • Falk says:

    First of all, thanks for the helpful demonstration of REST@work. I noticed one little error in one of your comments. It should be:

    “If a user is logged in and they want to change their password, link to edit_user_password_path(current_user)”

  • Matt says:

    Thanks, this is exactly what I needed… but can you please post your UserNotifier code?

  • Justin says:

    As requested, I’ve updated the post with the UserNotifier code. I hope this helps.

  • Alex says:

    I found your site on technorati and read a few of your other posts. Keep up the good work. I just added your RSS feed to my Google News Reader. Looking forward to reading more from you down the road!

  • Eric says:

    I’m pretty new to rails and I added this great bit of code to a restful_authentication project. My problem is with the validation. With your code, if you have the wrong old_password entered and everything else empty or wrong you only get a message about the old_password. If I enter the correct old_password and wrong other stuff, you still get the old_password is incorrect message.

    Since the user model is used to save the data to the db, I was trying to let the model handle the validation of all the fields but haven’t been able to figure it out. Do you have a suggestion on how to refactor the password change and validation code into the user model? I keep getting “nil where you didn’t expect it errors” since I still don’t fully understand all the scoping of the rails system.

    Thanks,
    Eric

  • Matthew says:

    Thanks Man! Worked great and is much more lightweight than a lot of what I’ve seen out there re: restful authentication plugin.

  • Ben Johnson says:

    This is a good post, but I think there is a big fundamental mistake here. What’s to keep me from resetting random user’s passwords? Let’s say I know someone’s login is “john”, but that is not my login. I don’t like John, so I will go and reset his password every day.

  • Justin says:

    @Ben The PasswordsController update method will only change the password for “current_user” which, in RESTful_authentication” is set once the user authenticates first. So, you could only change John’s password if you could authenticate as him.

  • Hi, thanks for this elegant and RESTful solution to this problem. I have one question however: it seems to me that the generation of a new password and the sending of the notification email would be better located in a Model method than in the controller.

    However, since the User model is generated by restful_authentication, I thought that might be your reason for putting this logic in the controller (keeping the restful_authentication stuff “pure”).

    In my application, I’ve put it in the User model, but was just wondering if there was something I’m missing?

  • Mikhailov says:

    Generate random password in Rails.

    http://www.railsgeek.com/2009/1/6/generate-random-password-in-rails

    I am using Restful_authentication plugin for one of my projects. As part of my user creation workflow, system should to generate a random password for the new user.

  • Hi,

    I follow your post but somehow in my case this

    @user.authenticated?(old_password)

    retur false.

    Any idea what’s wrong?

    Thanks

  • celio motta says:

    thanks!!! really helpful!!

  • Marc says:

    So I tried to make it so that when the user logs in with the @new_password that it would go to the edit password page so they have to change it. This is what I put it in but it doesn’t work…

    This is in the create method of the Sessions controller.

    if user.password == @new_password
    redirect_to edit_user_password_path(user)
    else
    redirect_back_or_default(‘/’)
    flash[:notice] = “You are now logged in.”
    end

    Any ideas?

  • Art says:

    Authentication on whether the passwords are of correct length are bypassed somehow. Any ideas?

  • Ron says:

    Thanks a lot, worked great.

  • Leave a Reply

    Justin has been obsessing over writing simple Web software using Ruby on Rails since 2007. He's also an entrepreneur and Lean Startup expert. Learn more

    View Justin Britten's profile on LinkedIn

    Subscribe to Justin Britten's blog Follow Justin Britten on Twitter Network with Justin Britten on LinkedIn
     
    Prefinery: Simple, online beta management software'

    Launch a private beta for your Web application in minutes. Prefinery takes care of collecting e-mail addresses, generating invitation codes, and sending invitations for your private beta. Your customers never leave your site, and e-mail invitations are sent from your address.

    Justin is Founder and CEO of Prefinery.