homeASCIIcasts

283: Authentification avec Sorcery 

(view original Railscast)

Other translations: En Ja Es

Other formats:

Written by Simon Courtois

Dans l'épisode 250 [regarder, lire], nous avons ajouté, from scratch, un mécanisme d'authentification dans une application Rails. Si vous préférez utiliser une solution tierce, il existe bon nombre de gems répondant à ce besoin. Dans cet épisode, nous allons jeter un œil sur celle nommée Sorcery.

Sorcery est une solution simple. Elle ne propose qu'une vingtaine de méthodes mais cela suffit amplement pour répondre à notre besoin. Malgré sa simplicité, elle est très complète et modulaire, nous pouvons donc choisir les parties que nous voulons utiliser, la réinitialisation de mot de passe, par exemple. Sorcery fonctionne à un niveau plus bas que les autres solutions d'authentification et nous laisse le soin d'écrire les couches contrôleurs et vues. Dans cet épisode, nous allons l'utiliser pour ajouter un mécanisme d'authentification à une application existante.

Démarrer

L'application avec laquelle nous allons travailler est très simple. Elle a une page d'accueil contenant un lien vers une page “secrète”. La page secrète est, pour le moment, visible par tout le monde. Nous voulons en restreindre l'accès aux utilisateurs connectés. Pour ce faire, nous devons ajouter une couche d'authentification à l'application et c'est la que Sorcery entre en jeu.

Notre application.

Sorcery est fournie sous forme de gem et s'installe de façon classique, Gemfile et bundle.

/Gemfile

gem 'sorcery'

Une fois cela fait, nous devons lancer la commande suivante pour créer l'initializer de Sorcery (nous reviendrons dessus plus loin).

$ rake sorcery:bootstrap

Nous allons ensuite générer une migration sorcery_migration. Elle va nous servir à choisir les modules de Sorcery que nous voulons inclure. Nous allons ajouter le module core, qui permet une authentification par mot de passe, et le module remember_me. Pour une liste complète des modules, consultez le README de Sorcery.

$ rails g sorcery_migration core remember_me
      create  db/migrate/20110914221626_sorcery_core.rb
      create  db/migrate/20110914221627_sorcery_remember_me.rb

La commande crée un certain nombre de fichiers de migration en fonction des modules choisis. Si nous regardons la migration sorcery_core, nous allons voir les attributs ajoutés à la table users.

/db/migrate/20110914221626_sorcery_core.rb

class SorceryCore < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :username,         :null => false  
      t.string :email,            :default => nil 
      t.string :crypted_password, :default => nil
      t.string :salt,             :default => nil
      t.timestamps
    end
  end

  def self.down
    drop_table :users
  end
end

Par défaut, la migration crée un champ username. Nous n'en avons pas besoin, nous allons donc commenter cette ligne.

/db/migrate/20110914221626_sorcery_core.rb

class SorceryCore < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      # t.string :username,         :null => false
      t.string :email,            :default => nil
      t.string :crypted_password, :default => nil
      t.string :salt,             :default => nil
      t.timestamps
    end
  end
end

Nous allons également devoir configurer Sorcery pour qu'il utilise le champs email plutôt que le champ username. Cela se fait en modifiant l'initializer de Sorcery. Au début de ce fichier, nous devons spécifier les modules que nous voulons activer. En dehors du module core, nous n'utilisons que le module remember_me, c'est donc le seul que nous devons ajouter.

/config/initializers/sorcery.rb
# The first thing you need to configure is which modules you need in your app.
# The default is nothing which will include only core features (password encryption, login/logout).
# Available submodules are: :user_activation, :http_basic_auth, :remember_me, 
# :reset_password, :session_timeout, :brute_force_protection, :activity_logging, :external
Rails.application.config.sorcery.submodules = [:remember_me]

# Rest of file omitted.

Il y a d'autres options de configuration que nous pouvons utiliser. Elles sont toutes documentées dans le fichier. Nous n'avons pas besoin de modifier la plupart d'entre elles. La seule que nous devons changer est username_attribute_name. Nous allons lui donner comme valeur :email afin de spécifier que c'est ce champ qui sera utilisé pour identifier les utilisateurs.

/config/initializers/sorcery.rb

config.user_config do |user|
  # -- core --
  user.username_attribute_name = :email
  # change default username
  # attribute, for example,
  # to use :email as the login.

  # Other options omitted.
end
# This line must come after the 'user config' block.
config.user_class = "User"
# define which model authenticates
# with sorcery.
end

À la fin du fichier, on peut trouver un élément de configuration qui permet de spécifier le nom du modèle utilisé par Sorcery pour l'authentification (User par défaut). Notre application n'a pas encore de modèle User, nous allons donc le créer. Nous avons déjà un fichier de migration pour spécifier les champs de User, nous allons donc dire à Rails de ne pas générer de migration pour ce modèle.

$ rails g model user --skip-migration

Pour activer Sorcery dans le modèle User, nous avons juste une ligne de code à ajouter.

/app/models/user.rb

class User < ActiveRecord::Base
  authenticates_with_sorcery!
end

Cela ajoute au modèle User un certain nombre de méthodes pour gérer l'authentification. Les attributs ne sont, cependant, ni validés ni protégés ; c'est à nous d'écrire ce code.

/app/models/user.rb

class User < ActiveRecord::Base
  authenticates_with_sorcery!

  attr_accessible :email, :password, :password_confirmation

  validates_confirmation_of :password
  validates_presence_of :password, :on => :create
  validates_presence_of :email
  validates_uniqueness_of :email
end

Il est maintenant temps de lancer les migrations pour que la table users soit crée.

$ rake db:migrate

Ajouter les Contrôleurs et les Vues

Maintenant que nous avons le modèle User, nous allons générer quelques contrôleurs pour l'accompagner. Nous allons commencer par UsersController pour gérer le processus d'inscription.

$ rails g controller users new

Nous allons également avoir besoin de SessionsController pour gérer les connexions.

$ rails g controller sessions new

Cela ressemble beaucoup à ce que nous avons fait dans l'épisode 250, sur l'authentification from scratch, nous allons donc passer rapidement sur ce sujet. UsersController va être relativement standard, construire un nouvel utilisateur dans l'action new et en créer un basé sur les informations fournies dans l'action create.

/app/controllers/users_controller.rb

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

Le template new va être assez standard également, un formulaire permettant de créer un nouvel utilisateur avec les champs email, password et password_confirmation ainsi qu'un peu de code pour afficher les erreurs de validation.

/app/views/users/new.html.erb

<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 %>

SessionsController est bien plus intéressant. Nous avons une action new mais celle-ci ne nécessite aucun code, nous allons donc voir son template. Ici, nous allons avoir besoin d'un simple formulaire de connexion avec des champs texte pour email et password, et une checkbox pour le champ remember_me.

/app/views/sessions/new.html.erb

<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="field">
    <%= check_box_tag :remember_me, 1, params[:remember_me] %>
    <%= label_tag :remember_me %>
  </div>
  <div class="actions"><%= submit_tag "Log in" %></div>
<% end %>

Nous devons écrire une action create pour gérer le formulaire de connexion. Sorcery fournit une méthode appelée login qui peut prendre trois paramètres, un nom d'utilisateur ou une adresse email, un mot de passe et la valeur du champ remember_me. Cette méthode va effectuer l'authentification et retourner un User en cas de succès. Nous pouvons donc vérifier son retour et rediriger vers la page d'accueil si un utilisateur est trouvé. Dans le cas contraire, nous allons afficher un message flash et afficher de nouveau le formulaire de connexion.

/app/views/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def new
  end
  
  def create
    user = login(params[:email], params[:password], ↵
      params[:remember_me])
    if user
      redirect_back_or_to root_url, :notice => "Logged in!"
    else
      flash.now.alert = "Email or password was invalid."
    end
  end
end

Au lieu d'utiliser redirect_to pour rediriger sur la page d'accueil lorsqu'un utilisateur est trouvé, nous allons utiliser une méthode fournie par Sorcery et appelée redirect_back_or_to. Son comportement est similaire à redirect_to mais si une URL est stockée par Sorcery, la redirection se fera vers cette URL plutôt que vers celle spécifiée dans le code. Ce fonctionnement est bien pratique car cela signifie que si un utilisateur tente d'accéder à une certaine page et qu'il est redirigé vers le formulaire de connexion, il sera redirigé vers cette page un fois connecté.

Il nous reste encore à fournir un moyen de se déconnecter. Nous allons donc ajouter une action destroy au contrôleur. Sorcery fournit une méthode logout et c'est tout ce dont nous avons besoin pour déconnecter un utilisateur. Une fois l'utilisateur déconnecter, nous allons le rediriger vers la page d'accueil.

/app/views/controllers/sessions_controller.rb

def destroy
  logout
  redirect_to root_url, :notice => "Logged out!"
end

Nous allons ensuite aller dans la configuration de nos routes et remplacer les actions générées par défaut par ceci :

/config/routes.rb

Auth::Application.routes.draw do
  get "logout" => "sessions#destroy", :as => "logout"
  get "login" => "sessions#new", :as => "login"
  get "signup" => "users#new", :as => "signup"
  resources :users
  resources :sessions
  get "secret" => "home#secret", :as => "secret"
  root :to => "home#index"
end

Nous avons à présent différentes routes nommées et deux ressources nous permettant de gérer toute la couche authentification.

Maintenant que nous avons ces nouvelles pages, nous allons avoir besoin de quelques liens pour que les utilisateurs puissent y accéder. Nous allons les ajouter au layout afin de les rendre disponibles sur toutes les pages. Nous pouvons utiliser la méthode current_user pour vérifier si l'utilisateur est connecté ou non. Si c'est le cas, nous allons afficher son adresse email suivie d'un lien “Log out”. Si, au contraire, il n'est pas connecté, nous allons afficher les liens d'inscription et de connexion.

/app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
<head>
  <title>Auth Example</title>
  <%= stylesheet_link_tag    "application" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body class="<%= params[:controller] %>">
  <div id="container">
    <div class="user_nav">
      <% if current_user %>
        Logged in as <%= current_user.email %>.
        <%= link_to "Log out", logout_path %>
      <% else %>
        <%= link_to "Sign up", signup_path %> or
        <%= link_to "Log in", login_path  %>.
      <% end %>
    </div>
    <% flash.each do |name, msg| %>
      <%= content_tag :div, msg, :id => "flash_#{name}" %>
    <% end %>
    <%= yield %>
  </div>
</body>
</html>

Nous sommes prêts à tester notre site. Si nous nous rendons sur la page d'accueil, nous allons maintenant voir les liens “Sign up” et “Log in”.

Chaque page contient maintenant les liens “Sign up” et “Log in”.

Si nous cliquons sur “Sign up”, nous allons voir le formulaire d'inscription et nous pouvons nous inscrire sur le site. Nous pouvons ensuite cliquer sur “Log in” et nous connecter avec nos identifiants.

Connexion.

Autorisation

Maintenant que nous sommes connectés sur le site, nous pouvons visiter la page secrète. Cependant, si nous nous déconnectons et tentons de visiter la page, nous pouvons toujours la voir. Nous devons ajouter un mécanisme d'autorisation pour restreindre l'accès à la page aux utilisateurs connectés.

La page secrète est une action du contrôleur HomeController. Nous pouvons utiliser un before_filter fournit par Sorcery, require_login, pour limiter l'accès aux actions. Nous allons l'utiliser pour protéger la page secrète.

/app/controllers/home_controller.rb

class HomeController < ApplicationController
  before_filter :require_login, :only => :secret

  def index
  end

  def secret
  end
end

Lorsque ce filtre est déclenché, Sorcery appelle sa méthode not_authenticated. Nous pouvons surcharger cette méthode dans ApplicationController pour contrôler ce qu'il se passe lorsque l'autorisation échoue. Nous allons rediriger vers la page de connexion et afficher un message d'alerte.

/app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery
  
  private
  def not_authenticated
    redirect_to login_url, :alert => "First log in to view ↵
    this page."
  end
  
end

Si nous essayons de consulter la page secrète sans être connectés, nous allons être redirigés vers la page de connexion et le message d'alerte sera affiché.

Si nous essayons de voir la page secrète sans être connectés, nous sommes redirigés.

Lorsque nous nous connectons, nous sommes redirigés vers la page secrète, Sorcery se souvenant de la page à laquelle nous tentions d'accéder avant d'être redirigés.

Une fois connectés, nous pouvons voir la page.

C'est tout pour cet épisode sur Sorcery. De nombreuses fonctionnalités n'ont pas été vues ici, pour en savoir plus, rendez-vous dans la documentation. Si vous cherchez une solution d'authentification fonctionnant à un niveau relativement bas, Sorcery vaut la peine d'y jeter un œil.