#270 Authentication in Rails 3.1
- Download:
- source codeProject Files in Zip (185 KB)
- mp4Full Size H.264 Video (8.86 MB)
- m4vSmaller H.264 Video (6.56 MB)
- webmFull Size VP8 Video (7.63 MB)
- ogvFull Size Theora Video (13.2 MB)
Rails 3.1 fournit de nouvelles fonctionnalités relatives à l'authentification et, dans cet épisode, nous allons vous montrer comment fonctionnent certaines d'entre elles. Pour ce faire, nous nous baserons sur une simple application dont une page est accessible par n'importe quelle personne connaissant l'URL.
Authentification HTTP Basic
Nous voulons ajouter une gestion d'accès à cette page de façon à ce qu'elle ne soit visible que par certains utilisateurs. Pour cela, nous devons ajouter une authentification et la façon la plus rapide de le faire est pas l'utilisation de HTTP Basic. Rails 3.1 fournit une nouvelle manière de la mettre en place, tout ce que nous avons besoin de faire, c'est de modifier le contrôleur de la page et ajouter un appel à une méthode appelée http_basic_authenticate_with
qui prend deux paramètres, :name
et :password
.
class SecretController < ApplicationController http_basic_authenticate_with :name => "frodo", :password => "thering" def index end end
Si nous voulons restreindre l'authentification à certaines actions, nous pouvons ajouter les options :only
(seulement) ou :except
(sauf). Bien évidemment, dans une application réelle, nous placerions le nom d'utilisateur et le mot de passe dans un fichier de configuration plutôt que de les laisser en dur dans le code. Pour cette démonstration cependant, cela fera l'affaire.
Lorsque nous visitons la page, nous allons voir l'invite de connexion et nous ne pourrons voir la page qu'en saisissant un nom d'utilisateur et un mot de passe corrects, ceux que nous avons spécifiés lors de l'appel à http_basic_authenticate_with
.
HTTP Basic est quelque peu sous utilisé. Il est très facile de le mettre en place pour rendre une partie de site privée et, avec Rails 3.1, c'est encore plus simple.
Utiliser secure_password
Il arrive que nous ayons besoin d'une authentification un peu plus poussée, pouvant gérer plusieurs utilisateurs. Pour simplifier ceci, Rails 3.1 fournit secure_password
.
Dans l'épisode 250 [regarder, lire], nous avons créé un système d'authentification depuis zéro et Rails 3.1 rend cela bien plus simple. Nous allons maintenant le faire et remplacer l'authentification HTTP Basic pour notre propre solution.
La première chose à faire est de générer un modèle User
avec les champs d'adresse e-mail et de mot de passe.
$ rails g model user email:string password_digest:string
Nous pouvons maintenant créer la table en base en effectuant une migration.
$ rake db:migrate
L'important ici est que nous appelons le champ prévu pour stocker le mot de passe password_digest
. Nous allons ensuite devoir ajouter un appel à has_secure_password
dans le modèle User
.
class User < ActiveRecord::Base has_secure_password end
Cela ajoute les méthodes permettant d'enregistrer et d'authentifier le mot de passe saisi, les validateurs pour le mot de passe et la confirmation de mot de passe ainsi que les fonctionnalités d'authentification. Le champ password_digest
, que nous avons créé plus tôt, est utilisé en arrière-plan pour stocker le mot de passe sous forme de hash.
Par défaut, il n'y a pas d'appel à validates_presence_of
pour le mot de passe, nous allons donc en ajouter un, effectué à la création d'un utilisateur.
class User < ActiveRecord::Base has_secure_password validates_presence_of :password, :on => :create end
En temps normal nous devrions également tester l'adresse e-mail mais nous ne le ferons pas ici.
Pour permettre aux utilisateurs de créer un compte, nous allons créer UsersController
.
$ rails g controller users
Le code du contrôleur est relativement standard et ressemble à ceci :
class UsersController < ApplicationController def new @user = User.new end def create @user = User.new(params[:user]) if @user.save redirect_to root_url, :notice => "Signed up!" else render "new" end end end
La vue new
contient un formulaire d'inscription.
<h1>Sign Up</h1> <%= form_for @user do |f| %> <% if @user.errors.any? %> <div class="error_messages"> <h2>Form is invalid</h2> <ul> <% for message in @user.errors.full_messages %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :email %> <%= f.text_field :email %> </div> <div class="field"> <%= f.label :password %> <%= f.password_field :password %> </div> <div class="field"> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation %> </div> <div class="actions"><%= f.submit %></div> <% end %>
Si nous essayons de nous inscrire et saisissons un mot de passe qui ne correspond pas à la confirmation, nous allons voir un message de validation fournit par has_secure_password
.
Lorsque nous saisissons un mot de passe correspondant, nous nous inscrivons correctement.
Les utilisateurs peuvent s'inscrire mais pas encore se connecter. Nous allons donc créer un formulaire de connexion avec des champs e-mail et mot de passe.
<h1>Log in</h1> <%= form_tag sessions_path do %> <div class="field"> <%= label_tag :email %> <%= text_field_tag :email, params[:email] %> </div> <div class="field"> <%= label_tag :password %> <%= password_field_tag :password %> </div> <div class="actions"><%= submit_tag "Log in" %></div> <% end %>
Notez que le formulaire utilise form_tag
plutôt que form_for
puisque nous n'éditons pas une ressource. Le formulaire envoie les informations à sessions_path
et nous avons donc besoin de créer SessionsController
.
$ rails g controller sessions
Ce contrôleur aura les méthodes new
, create
et destroy
afin que les utilisateurs puissent se connecter et se déconnecter.
class SessionsController < ApplicationController def new end def create if # authenticated? session[:user_id] = user.id redirect_to root_url, :notice => "Logged in!" else flash.now.alert = "Invalid email or password" render "new" end end def destroy session[:user_id] = nil redirect_to root_url, :notice => "Logged out!" end end
L'action create
n'est pas tout à fait complète car nous devons encore écrire le code déterminant si l'utilisateur se connectant est authentifié ou non. C'est là que nous pouvons tirer avantage du fait que Rails 3.1 nous fournit secure_password
.
La première chose que nous allons avoir besoin de faire est de récupérer l'utilisateur dont l'adresse e-mail correspond à la valeur saisie dans le formulaire. Nous pouvons ensuite appeler authenticate
, méthode fournie par has_secure_password
, sur cet utilisateur en passant en paramètre le mot de passe saisi dans le formulaire. Cette méthode va comparer le mot de passe au digest de celui stocké en base de données. Si aucune correspondance n'est trouvée, find_by_email
va retourner nil
de façon à ce que nous puissions tester que l'utilisateur existe avant de l'authentifier.
def create user = User.find_by_email(params[:email]) if user && user.authenticate(params[:password]) session[:user_id] = user.id redirect_to root_url, :notice => "Logged in!" else flash.now.alert = "Invalid email or password" render "new" end end
C'est tout ce dont nous avons besoin pour authentifier grâce à secure_password
. Nous pouvons tester cela en essayant de nous connecter. Si nous saisissons un mot de passe incorrect, nous ne pourrons pas nous authentifier.
Si nous nous connectons avec les bons identifiants, nous serons authentifiés et redirigés vers la page d'accueil.
À n'importe quel endroit de notre application, nous devons récupérer l'utilisateur actuellement authentifié. Pour ce faire, nous allons ajouter une méthode current_user
dans ApplicationController
et en faire un helper. Nous pourrons ainsi y faire appel dans nos vues. Cette méthode va récupérer l'utilisateur depuis la session.
/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base protect_from_forgery private def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] end helper_method :current_user end
Ce qui est fort sympathique, c'est que cette méthode d'authentification est aussi simple que le modèle User
, deux lignes de code.
class User < ActiveRecord::Base has_secure_password validates_presence_of :password, :on => :create end
En comparaison, la solution que nous avions créée dans l'épisode 250 avait un modèle User
bien plus complexe. Cela dit, il serait sage d'ajouter une ligne attr_accessible
au modèle de façon à ce que seuls l'e-mail, le mot de passe et la confirmation de mot de passe puissent être renseignés dans le formulaire d'inscription.
class User < ActiveRecord::Base attr_accessible :email, :password, :password_confirmation has_secure_password validates_presence_of :password, :on => :create end
Ajouter HTTPS
Si nous gérons l'authentification dans nos applications, nous ne voulons par voir les identifiants envoyés sur le réseau de façon lisible. C'est donc une bonne idée d'utiliser SSL et de passer par HTTPS. Avant Rails 3.1, cela devait être fait manuellement ou via un plugin. Il existe maintenant un façon bien plus simple de le faire.
Pour restreindre un contrôleur afin qu'il soit uniquement accessible en HTTPS, nous devons simplement ajouter un appel à la méthode de classe force_ssl
. Si nous voulons restreindre uniquement certaines actions, nous pouvons utiliser les options :only
ou :except
comme nous le ferions avec before_filter
.
class ApplicationController < ActionController::Base protect_from_forgery force_ssl private def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] end helper_method :current_user end
La méthode force_ssl
force uniquement HTTPS dans les modes test et production. Si nous relançons le serveur de notre application en mode production et rechargeons la page d'inscription, elle va tenter de nous rediriger vers la version SSL.
Cela échoue car notre serveur ne support par HTTPS mais, si c'était le cas, nous verrions la version sécurisée de cette page.
C'est tout pour cet épisode sur l'authentification dans Rails 3.1. Tous les ajouts autour de l'authentification rendent bien plus facile d'en ajouter à nos applications Rails.