#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)
Vamos a dedicar este episodio a repasar algunas de las nuevas funcionalidades que incorpora Rails 3.1 en lo relativo a la autenticación. Como demostración, tenemos una aplicación sencilla con una página que se puede acceder por cualquiera que conozca la URL.
Autenticación básica HTTP
Queremos añadir autorización a esta página de forma que sólo determinados usuarios puedan acceder a ella. Para esto tenemos que añadir autenticación, y la forma más rápida de hacerlo es con la autenticación básica de HTTP. Rails 3.1 nos permite implementarla de una nueva forma; tan sólo tenemos que modificar el controlador de la página y añadir una llamada a un nuevo método llamado http_basic_authenticate_with
que recibe las opciones :name
y :password
.
class SecretController < ApplicationController http_basic_authenticate_with :name => "frodo", :password => "thering" def index end end
Si queremos restringir la autenticación sólo a ciertas acciones podemos añadir las opciones :only
o :except
. Por supuesto en una aplicación real moveríamos el usuario y la clave a un fichero de configuración en lugar de tenerlo a plena vista en el código, pero para nuestra aplicación de ejemplo será más que suficiente.
Si ahora cargamos la página veremos un diálogo de contraseña y sólo podremos ver el contenido de la página si introducimos el usuario y clave correctos que especificamos en http_basic_authenticate_with
.
La verdad es que esta autenticación básica de HTTP se usa demasiado poco y es una forma rápida y fácil de hacer privada una parte de un sitio. Como se ha visto en Rails 3.1 es incluso más fácil de implementar.
Uso de secure_password
A veces nos hace falta un sistema de autorización un poco más robusto que pueda gestionar diferentes usuarios, y para ayudarnos con esto Rails 3.1 incorpora secure_password
.
En el episodio 250 [verlo, leerlo] creamos un sistema de autenticación partiendo desde cero y con Rails 3.1 es todavía más fácil hacerlo. Cambiemos la autenticación básica de HTTP por la nuestra.
Lo primero que debemos hacer es generar un modelo User
con campos para la dirección de correo y la clave.
$ rails g model user email:string password_digest:string
Con esto podemos crear la tabla de la base de datos ejecutando la migración.
$ rake db:migrate
Lo importante aquí es que el campo que almacenará la clave tiene el nombre password_digest
. A continuación tenemos que añadir una llamada a has_secure_password
en el modelo User
.
class User < ActiveRecord::Base has_secure_password end
Esto añade métodos que establecen y validan la clave introducida, añade validaciones para los campo de clave y confirmación y también añade la funcionalidad de autenticación. Por debajo se usa el campo password_digest
que creamos antes para almacenar la clave cifrada.
No existe por defecto una validación de presencia con validates_presence_of
para la clave, por lo que la añadiremos para comprobarlo cuando se crea un nuevo usuario.
class User < ActiveRecord::Base has_secure_password validates_presence_of :password, :on => :create end
Por lo general querremos validar la dirección de de correo pero este paso nos lo saltaremos por ahora.
Para que los usuarios puedan crear cuentas tenemos que escribir el controlador UsersController
.
$ rails g controller users
El código del controlador es muy sencillo y tiene este aspecto:
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 vista contiene un formulario de registro.
<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 ahora intentamos registrarnos e introducimos una clave y confirmación que no coinciden veremos el error de la validación que nos proporciona has_secure_password
.
Si la clave y su confirmación coinciden nos habremos registrado correctamente.
Los usuarios se pueden dar de alta pero todavía no pueden iniciar la sesión, así que crearemos un formulario de entrada con los campos de correo y clave.
<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 %>
Nótese que como no estamos editando un recurso tenemos que usar form_tag
en lugar de form_for
. El formulario se envía a sessions_path
, así que crearemos el controlador SessionsController
.
$ rails g controller sessions
Este controlador dispondrá de las acciones new
, create
y destroy
para que los usuarios puedan iniciar y terminar su sesión.
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
Todavía no hemos terminado con la acción create
porque todavía tenemos que escribir el código que determina si la clave introducida por el usuario es correcta o no. Aquí es donde podemos aprovechar la funcionalidad secure_password
de Rails 3.1.
Lo primero que tenemos que hacer es recuperar el usuario por la dirección de correo introducida en el formulario. Luego podemos invocar authenticate
sobre el usuario, que es un método que nos proporciona has_secure_password
, y le pasamos la clave que el usuario ha introducido en el formulario para que el método la compruebe contra la clave cifrada que hay en la base de datos. find_by_email
devolverá nil
si el usuario no se encuentra en la base de datos, así que comprobaremos si el usuario existe o no antes de comprobar la clave.
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
Esto es todo lo que hace falta hacer si queremos autenticarnos con secure_password
. Podemos probar este código intentando iniciar la sesión. Si introducimos una clave incorrecta veremos que la sesión no se inicia.
Si introducimos las credenciales adecuadas la autenticación nos redirigirá a la página principal.
Pero en la aplicación todavía tenemos que poder recuperar qué usuario es el que tiene la sesión iniciada, por lo que tenemos que crear un método llamado current_user
en ApplicationController
y hacer que sea un método helper para que podamos usarlo en las vistas. El método simplemente recupera el usuario guardado en la sesión.
/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
Lo mejor de este método de autenticación es la simplicidad del modelo User
, que sólo tiene dos líneas de código.
class User < ActiveRecord::Base has_secure_password validates_presence_of :password, :on => :create end
En comparación, la solución del episodio 250 tenía un modelo de usuario mucho más complejo. Dicho esto, sería prudente añadir la línea attr_accessible
al modelo para que por el formulario de registro sólo se puedan establecer correo, clave y confirmación.
class User < ActiveRecord::Base attr_accessible :email, :password, :password_confirmation has_secure_password validates_presence_of :password, :on => :create end
Cómo incluir HTTPS
En las aplicaciones en las que gestionemos autenticación no nos interesa enviar las credenciales en claro, por lo que es muy buena idea usar SSL sobre HTTPS. Antes de Rails 3.1 esto se tenía que hacer manualmente o con alguna extensión pero ahora tenemos la posibilidad de hacer esto de una forma mucho más fácil.
Para restringir el uso de un controlador a HTTPS tan sólo tenemos que añadir una llamada al método de clase force_ssl
. Si sólo queremos ejercer esta restricción sobre ciertas acciones de un controlador podemos utilizar las opciones :only
o :except
que funcionan igual que con 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
El método force_ssl
sólo fuerza HTTPS en los modos de pruebas y producción. Si reiniciamos la aplicación en modo de producción y recargamos la página de registro intentará redirigirnos a la versión SSL.
En nuestro servidor de desarrollo recibimos un fallo porque no soporta HTTPS pero si lo hicieramos con el de producción veríamos la versión segura de esta página.
Con esto cerramos este episodio sobre la autenticación en Rails 3.1, que con sus novedades hace que sea mucho más fácil de implementar en nuestras aplicaciones.