#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)
En el episodio 250 [verlo, leerlo] implementamos un esquema de autenticación en una aplicación Rails partiendo de cero. Si preferimos utilizar una solución ya construida hay varias gemas disponibles, en este episodio veremos una llamada Sorcery.
Sorcery es una solución muy sencilla. Tan sólo ofrece unos veinte métodos que se bastan para proporcionar todas las funciones de autenticación necesarias. A pesar de su simplicidad, es una solución completa y modular de forma que podemos activar sólo las partes que deseemos, por ejemplo la recuperación de contraseñas, registro de actividad, etc. Sorcery funciona a un nivel más bajo que otras gemas de autenticación dejando de nuestra parte la tarea de escribir las capas de vista y controlador. En este episodio lo utilizaremos para añadir autenticación a una aplicación Rails ya existente.
Empezando
La aplicación con la que vamos a trabajar es muy sencilla, tiene una página de bienvenida con un enlace a una página secreta. Dicha página puede ser vista por cualquiera pero queremos restringir el acceso sólo a los usuarios que hayan iniciado sesión. Usaremos Sorcery para implementar este tipo de autenticación.
Sorcery se distribuye como gema y se instala de la forma habitual añadiendo una referencia en el Gemfile
y ejecutando a continuación bundle
.
gem 'sorcery'
Después de que Bundler haga su trabajo tenemos que ejecutar la siguiente orden para añadir el fichero de inicialización de Sorcery (que más adelante veremos).
$ rake sorcery:bootstrap
Lo siguiente es generar una migración con sorcery_migration
. Aquí podemos escoger los módulos de Sorcery que deseamos incluir. Nosotros incluiremos la funcionalidad core
que implementa una autenticación simple basada en clave y el módulo remember_me
. Se puede ver un listado completo de los módulos en el 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
Esta orden crea varios ficheros de migración (dependiendo de los módulos que hayamos escogido), si examinamos la migración sorcery_core
veremos los atributos que se añaden a la tabla 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
Por defecto la migración crea un campo username
, que no queremos porque ya tenemos el email
, así que dejaremos esa línea comentada.
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
A continuación tenemos que configurar Sorcery para que utilice email
en lugar de username
. Esto se hace modificando el código de inicialización de Sorcery. En la parte superior de este archivo tenemos que especificar los módulos que queremos activar, como además de core
estamos usando remember_me
será este el único que tenemos que añadir.
/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.
Hay otras muchas opciones de configuración que podemos activar, documentadas en el mismo archivo. La mayoría no tendremos que tocarlas pero una que sí que tenemos que cambiar es username_attribute_name
. Activaremos esta opción y cambiaremos su valor para que sea :email
dado que ese es el campo que queremos usar para identificar a los usuarios.
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
Al final del archivo existe un valor de configuración que especifica el nombre del modelo que Sorcery utiliza para la autenticación y que por defecto es User
. Como nuestra aplicación todavía no tiene un modelo para los usuarios tenemos que crearlo, pero dado que ya tenemos la migración creada con los campos que Sorcery necesita tendremos que decirle a Rails que para este nuevo modelo no genere migración.
$ rails g model user --skip-migration
Tan sólo tenemos que añadir esta línea de código para activar Sorcery en el modelo User
.
class User < ActiveRecord::Base authenticates_with_sorcery! end
Esta línea añade varios métodos al modelo User
que sirven para gestionar la autenticación. Sin embargo no añade validaciones o protege ningún atributo, esta tarea nos corresponde a nosotros.
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
Ya podemos lanzar las migraciones para que se cree la tabla de usuarios.
$ rake db:migrate
Controladores y vistas
Ahora que hemos configurado el modelo User
debemos generar algunos controladores que lo acompañen. Primero crearemos el UsersController
para gestionar el proceso de alta.
$ rails g controller users new
Nos hará falta también un SessionsController
para controlar los inicios de sesión.
$ rails g controller sessions new
Todo esto es muy parecido a como hicimos en el episodio 250 cuando creamos la autenticación desde cero, por lo que aquí iremos más deprisa. El controlador UsersController
será bastante normal, creando un nuevo User
en la acción new
y creando un nuevo User
a partir de los parámetros recibidos en la acción 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
No habrá sorpresas con la plantilla para new
que es tan sólo un formulario para crear un usuario con campos llamados email
, password
y password_confirmation
así como el código que muestra los errores de validación.
<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 %>
La cosa se pone más interesante con SessionsController
. También tenemos una acción new
donde no hace falta añadir más código así que pasaremos directamente a la plantilla donde nos hace falta poner un formulario de inicio de sesión con los campos email
y password
así como un checkbox
para el campo 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 %>
Tenemos que escribir la acción create
para procesar el formulario de inicio de sesión. Sorcery nos da un método llamado login
que recibe tres parámetros: un nombre de usuario o dirección de correo, la clave introducida y el valor de la casilla remember_me
. Este método realizará la autenticación y devolverá un User
si es que se ha encontrado uno que coincida. Podemos comprobarlo y redirigir a la página pricipal en ese caso, y si no mostrar un mensaje de error junto con el formulario de inicio de sesión.
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
En lugar de utilizar redirect_to
para llevar al usuario a la página principal cuando éste se autentica correctamente, utilizaremos un método de Sorcery llamado redirect_back_or_to
que se comporta de forma similar a redirect_to
pero con la diferencia de que si Sorcery ha guardado una URL redirigirá a esta en lugar de a la que se haya especificado en el código. Esto es muy útil porque si exigimos que el usuario inicie sesión cuando visite cierta página, Sorcery lo llevará automáticamente a esa página cuando se autentique correctamente.
Todavía nos hace falta la manera de que el usuario cierre la sesión, así que añadiremos la acción destroy
al controlador. Sorcery tiene el método logout
, que es todo lo que tenemos que llamar para cerrar dicha sesión. Tras esto redirigimos al usuario a la portada del sitio web.
def destroy logout redirect_to root_url, :notice => "Logged out!" end
A continuación nos iremos al fichero de rutas y ajustaremos todos los cables, cambiando las acciones que han sido generadas automáticamente y poniendo estas rutas:
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
Con esto ya tenemos varias rutas y un par de recursos con los que podemos gestionar toda la funcionalidad relacionada con la autenticación.
Con estas nuevas páginas implementadas sólo nos falta poner los enlaces para que los usuarios puedan llegar a ellas. Los añadiremos en la página de layout para que sean visibles en toda la aplicación. Podemos usar el método current_user
para ver si tenemos una sesión iniciada en cuyo caso mostraremos la dirección de correo y el enlace para salir de la sesión. Si no hay usuario mostraremos los enlaces a las páginas de alta y de inicio de sesión.
<!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>
Con esto ya está todo listo y podemos comprobar el sitio. Si vamos a la página principal veremos los enlaces de registro y de inicio de sesión.
Si hacemos clic en “Sign up” veremos el formulario de registro donde podemos darnos de alta. Luego podemos hacer clic en “Log in” para entrar con nuestros datos de inicio de sesión.
Autorización
Ahora que tenemos la sesión iniciada ya podemos visitar la página secreta. Pero si cerramos la sesión y visitamos la página secreta veremos que seguimos pudiendo acceder a ella. Nos hace falta implementar la autorización necesaria para que la página sólo pueda ser accedida por usuarios con sesión iniciada.
La página secreta es una acción de HomeController
. Para limitar el acceso a las acciones podemos utilizar un before_filter
de Sorcery llamado require_login
.
class HomeController < ApplicationController before_filter :require_login, :only => :secret def index end def secret end end
Cuando se activa este filtro Sorcery llama a su método not_authenticated
, que podemos redefinir en ApplicationController
para poder controlar lo que ocurre cuando falla la autorización. De momento nosotros optaremos por dirigir a la página de inicio de sesión y mostrar un mensaje de alerta.
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 intentamos visitar la página secreta sin una sesión abierta seremos llevados a la página de inicio de sesión, mostrándose un mensaje de alerta.
Tras iniciar la sesión volveremos a la página secreta porque Sorcery recordará la página a la que estábamos tratando de acceder cuando nos llevó al inicio de sesión.
Con esto terminamos nuestro episodio sobre Sorcery. Tiene muchas más funciones que hoy no hemos visto pero que se pueden encontrar en su documentación. Puede que nos merezca la pena considerar Sorcery si buscamos una solución de autenticación que trabaje a un nivel un poco más bajo que otras gemas.