Réinitialiser le mot de passe d'un utilisateur avec Authlogic

publié le 22/03/2010 par Matthieu Sadouni

Il arrive fréquemment qu'un utilisateur oublie son mot de passe. Nous allons lui permettre de le réinitialiser de manière simple et sûre grâce à Authlogic.

Cet article s’appuie sur le tutorial de Ben Johnson et fait suite aux précédents articles sur l’inscription et sur l’activation d’un compte utilisateur.

Le processus de réinitialisation d’un mot de passe suit les étapes suivantes :

  1. l’utilisateur demande la réinitialisation de son mot de passe
  2. un email contenant un lien de réinitialisation lui est envoyé
  3. l’utilisateur clique sur le lien et arrive sur un formulaire où saisir son nouveau mot de passe
  4. une fois le mot de passe modifié l’utilisateur est automatiquement connecté et le lien ayant servi à la réinitialisation est expiré

Vérification de la présence des champs nécessaires

Nous allons avoir besoin de deux champs email et perishable_token sur le modèle User. Si vous avez suivi le tutorial sur l’inscription d’un utilisateur ces champs sont déjà présents et vous pouvez passer à l’étape suivante. Sinon, ajoutons-les à l’aide d’une migration.

Commençons par générer le fichier de migration :

    script/generate migration add_users_passwords_reset_fields
    

Nous ajoutons ensuite au fichier les champs nécessaires :

# db/migrate/xxx_add_users_passwords_reset_fields.rb
    class AddUsersPasswordResetFields < ActiveRecord::Migration
      def self.up
        add_column :users, :perishable_token, :string, :default => '', :null => false
        add_column :users, :email, :string, :default => '', :null => false
        add_index :users, :perishable_token
        add_index :users, :email
      end

      def self.down
        remove_column :users, :perishable_token
        remove_column :users, :email
      end
    end
    

Appliquons enfin la migration :

    rake db:migrate
    

Le modèle User est maintenant prêt, nous pouvons passer à l’étape suivante.

Demande de réinitialisation du mot de passe

Nous allons avoir besoin d’un formulaire permettant à un utilisateur de saisir son email pour recevoir le lien de réinitialisation. Pour nous conformer à la méthode REST, nous allons créer une resource PasswordReset. Une demande de réinitialisation correspond alors à la création d’une PasswordReset (action create du contrôleur PasswordResetsController). Nous créons ce contrôleur :

    script/generate controller password_resets
    

Nous y ajoutons deux actions new et create correspondant respectivement à l’affichage du formulaire et à son traitement :

# app/controllers/password_resets_controller.rb
    class PasswordResetsController < ApplicationController
      def new
      end

      def create
        @user = User.find_by_email(params[:email])
        if @user
          @user.deliver_password_reset_instructions!
          flash[:succes] = "Les instructions vous permettant de réinitialiser votre mot de passe vous ont été envoyées par email."
          redirect_to root_url
        else
          flash[:avertissement] = "Aucun utilisateur n'a été trouvé avec cette adresse email."
          render :action => :new
        end
      end
    end
    

La méthode deliver_password_reset_instructions! initialise le champ perishable_token et envoie un email contenant le lien de réinitialisation. Voyons le code de cette méthode dans le modèle User :

# app/models/user.rb
    class User < ActiveRecord::Base
      def deliver_password_reset_instructions!
        reset_perishable_token!
        UserMailer.deliver_password_reset_instructions(self)
      end
    end
    

La méthode reset_perishable_token! est fournie par Authlogic, elle génère un nouveau perishable_token et sauvegarde l’enregistrement.

Nous ajoutons au UserMailer créé dans le précédent article sur l’inscription d’un utilisateur le code suivant :

# app/models/user_mailer.rb
    class UserMailer < ActionMailer::Base
      def password_reset_instructions(user)
        subject "Instructions pour la réinitialisation de votre mot de passe"
        from "no-reply@example.com"
        recipients user.email
        sent_on Time.now
        body :edit_password_reset_url => edit_password_reset_url(user.perishable_token)
      end
    end
    

L’adresse edit_password_reset_url correspond au formulaire de saisie de nouveau mot de passe que nous allons créer dans un instant. Il ne nous manque plus pour cette étape que le contenu de l’email, du formulaire et la route permettant d’accéder au différentes actions. Commençons par l’email :

# app/views/user_mailer/password_reset_instructions.erb
    Une demande de réinitialisation de mot de passe a été faite pour votre compte sur le site example.com.

    Si vous n'avez pas effectué cette demande, vous pouvez ignorer cet email

    Si vous avez effectué cette demande, il vous suffit de cliquer sur le lien ci-dessous pour indiquer votre nouveau mot de passe.

    <%= @edit_password_reset_url %>

    Si l'adresse ci-dessus ne fonctionne pas, tentez de la copier / coller directement dans votre navigateur.

    Si vous rencontrez un problème, n'hésitez pas à nous contacter.
    

Puis le formulaire :

# app/views/password_resets/new.erb
    <h1>Mot de passe oublié ?</h1>

    <p>Indiquez votre email ci-dessous et les instructions vous permettant de réinitialiser votre mot de passe vous seront envoyées.</p>

    <% form_tag password_resets_path do %>
      <div>
        <label>Email</label>
        <%= text_field_tag "email" %>
      </div>
      <div>
        <%= submit_tag "Réinitialiser mon mot de passe" %>
      </div>
    <% end %>
    

Et enfin les routes :

# config/routes.rb
    map.resources :password_resets
    

Un utilisateur peut maintenant demander la réinitialisation de son mot de passe vers laquelle nous pouvons créer un lien avec la méthode new_password_reset_url. Voyons maintenant ce qu’il se passe une fois l’email reçu.

Réinitialisation du mot de passe

Lorsque l’utilisateur clique sur le lien contenu dans l’email, il est envoyé sur l’action edit du contrôleur PasswordResets pour modifier son mot de passe. Nous lui ajoutons le code nécessaire :

# app/controllers/password_resets_controller.rb
    before_filter :load_user_using_perishable_token, :only => [:edit, :update]

    def edit
    end

    def update
      @user.password = params[:user][:password]
      @user.password_confirmation = params[:user][: password_confirmation]
      if @user.save
        flash[:notice] = "Le mot de passe a bien été modifié"
        redirect_to account_url
      else
        flash[:avertissement] = "Le mot de passe n'a pas pu être modifié"
        render :action => :edit
      end
    end

    private

    def load_user_using_perishable_token
      @user = User.find_using_perishable_token(params[:id])
      unless @user
        flash[:notice] = "Nous sommes désolés, nous n'avons pas pu retrouver votre compte. " +
        "Si vous rencontrez un problème, tentez de copier / coller l'adresse " +
        "depuis l'email dans votre navigateur, ou recommencez " +
        "le processus de réinitialisation du mot de passe"
        redirect_to root_url
      end
    end
    

La méthode load_user_using_perishable_token permet de retrouver l’utilisateur à partir du perishable_token contenu dans le lien. Elle utilise la méthode find_using_perishable_token fournie par Authlogic ; cette méthode s’assure que le token utilisé n’est pas vide et est toujours valide, Authlogic expirant automatiquement les tokens au bout de 10 minutes.

L’utilisateur est retrouvé avant l’appel aux deux actions edit et update grâce au before_filter. Si la modification du mot de passe réussit, l’utilisateur est automatiquement connecté grâce à Authlogic.

Nous créons le formulaire de réinitialisation du mot de passe :

# app/views/password_resets/edit.erb
    <h1>Modifier mon mot de passe</h1>

    <% form_for @user, :url => password_reset_path, :method => :put do |f| %>
      <%= f.error_messages %>
      <div>
        <%= f.label :password, "Mot de passe" %>
        <%= f.password_field :password %>
      </div>
      <div>
        <%= f.label :password_confirmation, "Saisissez à nouveau votre mot de passe" %>
        <%= f.password_field :password_confirmation %>
      </div>
      <div>
        <%= f.submit "Modifier mon mot de passe et me connecter" %>
      </div>
    <% end %>
    

Il ne nous reste plus qu’à restreindre l’accẻs en s’assurant que seul un utilisateur non connecté peut réinitialiser son mot de passe. Nous allons pour cela ajouter un autre before_filter appelant une méthode require_no_user présente dans ApplicationController :

# app/controllers/application_controller.rb
    def require_no_user
      if current_user
        store_location
        flash[:avertissement] = "Vous devez être déconnecté pour accéder à cette page"
        redirect_to root_url
        return false
      end
    end

    def store_location
      session[:return_to] = request.request_uri
    end
    
# app/controllers/password_resets_controller
    before_filter :require_no_user
    

Nos utilisateurs ont maintenant la possibilité de réinitialiser leur mot de passe de manière simple, sûre et conforme à la méthode REST.

blog comments powered by Disqus