homeASCIIcasts

209: Introduccion a Devise 

(view original Railscast)

Other translations: En It Cn Ja

Written by Juan Lupión

A lo largo de todos estos episodios hemos cubierto diferentes soluciones de autenticación y hoy veremos otra más llamada “Devise” que últimamente viene ganando popularidad. Devise está basado Warden, una solución de autenticación para Rack y de hecho lo usa por debajo pero no es necesario tener conocimientos de ningún tipo acerca de Warden para seguir este episodio porque vamos a tratar con Devise directamente.

Devise gestiona la autenticación a todos los niveles. Si ya conocemos Authlogic (que vimos sen el episodio 160 [verlo, leerlo] sabremos que cubre la capa del modelo. Por el contrario Devise es un engine de Rails y cubre también vistas y controladores. La arquitectura de Devise es modular y consta de once módulos cada uno de los cuales cubre un aspecto diferente de la autenticación. Por ejemplo el módulo Rememberable recuerda la autenticación del usuario en una cookie mientras que otro módulo, Recoverable, se ocupa de reiniciar la clave del usuario enviando instrucciones por correo. Este enfoque hace que sea muy fácil escoger exactamente las funcionalidades de autenticación que queramos utilizar.

Añadiendo autenticación a una aplicación

Veamos qué necesitamos para que Devise funcione en una aplicación. A continuación se muestra una captura de pantalla de una aplicación sencilla de gestión de proyectos, escrita en Rails 3.0, sobre la que usaremos Devise para añadir un modelo User y su correspondiente autenticación.

Nuestra aplicación de gestión de proyectos.

Aunque Devise funciona bastante bien con Rails 3, hay una serie de instrucciones específicas de instalación que hay que seguir para instalar la versión correcta, que debería ser la versión actual (1.1.rc0). Si queremos usar Devise con aplicaciones Rails 2.3 necesitamos instalar la versión 1.0.6 porque la versión 1.1 no es compatible hacia atrás con Rails 2.

Dado que estamos trabajando con una aplicación Rails 3, añadiremos una referencia a Devise en el fichero Gemfile de nuestra aplicación teniendo cuidado de especificar la versión correcta.

/Gemfile

gem 'devise', '1.1.rc0'

Una vez hecho esto ejecutaremos bundle para instalar la gema y sus dependencias

bundle install

El siguiente paso es ejecutar el generador de instalación.

$ rails generate devise_install
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml

===============================================================================

Some setup you must do manually if you haven't yet:

  1. Setup default url options for your specific environment. Here is an
     example of development environment:

       config.action_mailer.default_url_options = { :host => 'localhost:3000' }

     This is a required Rails configuration. In production is must be the
     actual host of your application

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root :to => "home#index"

===============================================================================

Este comando genera un par de ficheros: un inicializador y un archivo con traducciones que contiene todos los mensajes que Devise necesita mostrar. Tras esto aparecen dos pasos manuales de instalación que deben seguirse. El primero es configurar la opción host para el mailer de la aplicación, mientras que el segundo indica que debemos tener una ruta raíz. Como en nuestro caso la aplicación ya tiene una ruta raíz no vamos a tener que hacer nada, pero sí que vamos a tener que seguir el primer paso. Lo haremos copiando la línea que aparece en las instrucciones anteriores al bloque correspondiente el fichero de configuración de desarrollo.

/config/environments/development.rb

config.action_mailer.default_url_options = { :host => 'localhost:3000' }

Con esta línea configuramos la opción host a locahost. Cuando vayamos a poner la aplicación en el entorno de producción tendremos que poner el valor a nuestro nombre de dominio en el archivo production.rb.

Creación de modelo de usuario con Devise

Para la autenticación necesitamos un modelo User, y Devise proporciona un generador para hacer justo eso. Su uso no es imprescindibile pero nos ahorrará un par de pasos.

$ rails generate devise User
      invoke  active_record
      create    app/models/user.rb
      invoke    test_unit
      create      test/unit/user_test.rb
      create      test/fixtures/users.yml
      inject  app/models/user.rb
      create  db/migrate/20100412200407_devise_create_users.rb
       route  devise_for :users

Este generador crea varios elementos interesantes: un fichero con el modelo, una migración y una ruta devise_for. Pasemos a verlo todo.

El fichero de modelo generado tene este aspecto:

/app/models/user.rb

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :token_authenticatable, :lockable, :timeoutable and :activatable
  # :confirmable
  devise :database_authenticatable, :registerable, 
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :password, :password_confirmation
end

El modelo User es parecido a cualquier otro modelo ActiveRecord pero tiene una llamada al método devise, que es donde sucede la magia de la autenticación. El método devise recibe como argumentos una lista de los módulos que queremos que sean soportados en nuestra aplicación, en nuestro ejemplo vemos los módulos que veíamos antes: :rememberable y :recoverable. Es fácil añadir o quitar módulos de esta lista, con lo que personalizaríamos la funcionalidad de autenticación de Devise para ajustarla a las necesidades de nuestra aplicación. Por ejemplo hemos quitado :confirmable porque no queremos que nuestros usuarios tengan que confirmar su correo.

Obsérvese que la clase User también tiene un método attr_accesible que enumera los atributos que el usuario puede cambiar desde la aplicación. Si en nuestro modelo necesitamos poner nuestras propias columnas también las añadiríamos aquí.

Veamos ahora el fichero de migración que ha sido generado automáticamente.

class DeviseCreateUsers < ActiveRecord::Migration
  def self.up
    create_table(:users) do |t|
      t.database_authenticatable :null => false
      # t.confirmable
      t.recoverable
      t.rememberable
      t.trackable
      # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both

      t.timestamps
    end

    add_index :users, :email,                :unique => true
    # add_index :users, :confirmation_token,   :unique => true
    add_index :users, :reset_password_token, :unique => true
    # add_index :users, :unlock_token,         :unique => true
  end

  def self.down
    drop_table :users
  end
end

La particularidad de esta migración es que invoca sobre la tabla un método diferente para las columnas que necesita cada módulo. Como no queremos usar el módulo confirmable hemos comentado su método correspondiente y también hemos borrado el índice relativo al token de confirmación porque dicha columna no existirá en la tabla.

Una vez configurada la migración según los módulos que queremos utilizar podemos ejecutar la migración de base de datos.

rake db:migrate

Por último tenemos la ruta devise_for que el generador nos puso en el fichero de rutas. Si ejecutamos rake routes podemos las rutas generadas por esa línea de código.

    new_user_session   GET    /users/sign_in                 {:controller=>"devise/sessions", :action=>"new"}
          user_session POST   /users/sign_in                 {:controller=>"devise/sessions", :action=>"create"}
  destroy_user_session GET    /users/sign_out                {:controller=>"devise/sessions", :action=>"destroy"}
                       POST   /users/password(.:format)      {:controller=>"devise/passwords", :action=>"create"}
         user_password PUT    /users/password(.:format)      {:controller=>"devise/passwords", :action=>"update"}
     new_user_password GET    /users/password/new(.:format)  {:controller=>"devise/passwords", :action=>"new"}
    edit_user_password GET    /users/password/edit(.:format) {:controller=>"devise/passwords", :action=>"edit"}
                       POST   /users(.:format)               {:controller=>"devise/registrations", :action=>"create"}
                       PUT    /users(.:format)               {:controller=>"devise/registrations", :action=>"update"}
     user_registration DELETE /users(.:format)               {:controller=>"devise/registrations", :action=>"destroy"}
 new_user_registration GET    /users/sign_up(.:format)       {:controller=>"devise/registrations", :action=>"new"}
edit_user_registration GET    /users/edit(.:format)          {:controller=>"devise/registrations", :action=>"edit"}

No resulta muy legible pero puede verse que hay varias rutas de autenticación para entrar y salir de la aplicación, reiniciar las claves, darse de alta como nuevo usuario y resetear un perfil. Por supuesto podemos cambiar estas rutas, en caso de que necesitemos cambiarlas.

Ya podemos acceder a nuestra autenticación a través de estas rutas. Si entramos en /users/sign_up veremos el siguiente formulario para registrarnos como nuevo usuario:

El formulario de alta.

Si rellenamos el formulario y pulsamos el botón de “Sign up” entraremos en la aplicación con sesión iniciada. Podemos salirnos visitando /users/sign_out, pero si intentamos iniciar sesión de nuevo con /users/sign_in y rellenamos el formulario con nuestro usuario y clave veremos un error.

Al iniciar sesión aparece un error en Rails 3 beta 2

Esto se debe a un problema ajeno a Devise y específico de Rails 3.0 beta 2. Pero por suerte es fácil de corregir: en el archivo /config/initializers/cookie_verification_secret.rb hay una línea de código que establece la clave secreta para verificar las cookies firmadas.

/config/initalizers/cookie_verification_secret.rb

# Be sure to restart your server when you modify this file.

# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random, 
# no regular words or you'll be exposed to dictionary attacks.
Rails.application.config.cookie_secret = '3b161f7668f938d1aeb73e1137964f8d5ebaf32b9173c2130ecb73b95b610702b77370640dce7e76700fb228f35f7865ab2a5ccd22d00563504a2ea9c3d8dffe'

Lo único que tenemos que hacer es borrar esta línea y ponerla en /config/application.rb.

/config/application.rb

require File.expand_path('../boot', __FILE__)
require 'rails/all'

Bundler.require(:default, Rails.env) if defined?(Bundler)

module ProjectManage
  class Application < Rails::Application
    config.filter_parameters << :password
    config.cookie_secret = '3b161f7668f938d1aeb73e1137964f8d5ebaf32b9173c2130ecb73b95b610702b77370640dce7e76700fb228f35f7865ab2a5ccd22d00563504a2ea9c3d8dffe'
  end
end

Para que este cambio surta efecto tenemos que reiniciar el servidor, pero una vez hecho esto ya deberíamos poder iniciar sesión correctamente.

Sesión iniciada.

Ahora que tenemos la autenticación configurada y funcionando podemos empezar a mejorarla. Nos gustaría tener un enlace en la parte superior de la página que nos diga si tenemos sesión iniciada y otro que nos permita iniciar y cerrar una sesión.

Lo podemos hacer modificando el fichero de layout de la aplicación de forma que los enlaces sean visibles en todas las páginas. Añadamos las siguientes líneas justo después del código que muestra los mensajes flash:

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

<div id="user_nav">
  <% if user_signed_in? %>
    Signed in as <%= current_user.email %>. Not you?
    <%= link_to "Sign out", destroy_user_session_path %>
  <% else %>
    <%= link_to "Sign up", new_user_registration_path %> or
    <%= link_to "Sign in", new_user_session_path %>
  <% end %>
</div>

En este código tenemos una sentencia if/else para poder mostrar un mensaje diferente dependiendo de si el usuario que está viendo el sitio tiene iniciada sesión o no. Esto lo podemos averiguar llamando a un método que nos proporciona Devise llamado user_signed_in? y que devolverá true si el usuario tiene sesión iniciada, en cuyo caso queremos mostrar su dirección de correo y un enlace para cerrar la sesión. Podemos mostrar la dirección de correo del usuario actual llamando al método current_user para recuperar el objeto User del usuario y mostrar su correo. Para mostrar la ruta adecuada para el enlace de cierre de sesión podemos mirar en las rutas generadas con rake routes, donde podemos ver destroy_user_session que mapea a /users/sign_out, por lo que para generar la URL correcta podemos utilizar destroy_user_session_path.

También podemos utilizar new_user_registration_path y new_user_session_path para crear los enalces para darse de alta o iniciar seesión. Una vez puestos ya podemos recargar la página y veremos la información el usuario en la parte superior.

Ya se muestran los datos del usuario en la parte superior de cada página.

Si seguimos el enlace “Sign out” veremos que ahora aparecen los enlaces “Sign up” y “Sign in” porque no tenemos la sesión iniciada.

Cuando el usuario no está registrado aparecen enlaces para darse de alta o iniciar la sesión.

Como puede verse, con Devise es muy sencillo armar toda la funcionalidad de autenticación, añadiendo sin esfuerzo la capacidad de registrar nuevos usuarios y permitirles iniciar y cerrar sesión. También hay otras partes que no hemos cubierto, como por ejemplo la página de reinicio de contraseña. Así mismo, si hubiésemos conservado el módulo confirmable Devise también habría generado el formulario y lógica correspondientes.

Página de reinicio de clave.

Aunque la generación automática de tantos formularios es bastante útil probablemente queramos personalizar su aspecto para ajustarlos a nuestra aplicación. Por suerte esto es fácil de hacer con Devise y lo veremos en el próximo episodio.