#283 Authentication with Sorcery
- Download:
- source codeProject Files in Zip (94.3 KB)
- mp4Full Size H.264 Video (24.6 MB)
- m4vSmaller H.264 Video (12.8 MB)
- webmFull Size VP8 Video (16 MB)
- ogvFull Size Theora Video (30.1 MB)
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.
Sorcery est fournie sous forme de gem et s'installe de façon classique, Gemfile
et bundle
.
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
.
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.
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.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.
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.
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
.
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.
<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
.
<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.
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.
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 :
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.
<!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”.
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.
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.
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.
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é.
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.
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.