homeASCIIcasts

209: Introduzione a Devise 

(view original Railscast)

Other translations: En Es Cn

Other formats:

Written by Andrea Salicetti

Abbiamo trattato di molte soluzioni per l’autenticazione nel corso di questi episodi ed oggi ve ne mostriamo un’altra. Questa di oggi si chiama “devise” e ha guadagnato i favori e la popolarità fra la community Rails di recente. Devise è basato su Warden, una soluzione di autenticazione basata su Rack, e la usa dietro le quinte. Non dovrete conoscere nulla riguardo a Warden per seguire questo episodio dal momento che avremo a che fare direttamente con devise.

Devise gestisce l’autenticazione su tutto lo stack. Se conoscete già Authlogic, che è stato trattato nell’episodio 160 [guardalo, leggilo] saprete già che copre solo il livello di modello. A titolo di confronto, devise è un motore Rails e copre anche l’autenticazione sui controller e sulle viste. Devise è modulare e al momento è formato da undici moduli, ciascuno dei quali fornisce un aspetto differente di autenticazione. Per esempio uno dei moduli, Rememberable, si ricorda l’autenticazione dell’utente memorizzata in un cookie mentre un’altra, Recoverable, si occupa di resettare la password dell’utente e di mandare le istruzioni per il reset via email. Questo approccio rende semplice la scelta di quali parti dell’autenticazione si vogliono usare (e quali no) all’interno della propria applicazione.

Aggiungere l’autenticazione ad una applicazione

Diamo un’occhiata a ciò che serve per far funzionare devise in un’applicazione. Di sotto è presente uno screenshot preso da una semplice applicazione di gestione progetti, scritta in Rails 3.0. Useremo devise per aggiungere un modello User ed un po’ di logica di autenticazione a questa applicazione.

La nostra applicazione di gestione progetti.

Devise funziona bene con Rails 3, ma si devono seguire alcune specifiche istruzioni durante l’installazione per prendere la versione corretta. Per le applicazioni Rails 3, dovrebbe essere installata l’ultima versione disponibile, attualmente la 1.1.rc1. Se invece si vuole usare devise con un’applicazione Rails 2.3, allora si deve usare la versione 1.0.6, poichè la versione 1.1 non è retrocompatibile con Rails 2.

Noi lavoreremo come detto con un’applicazione Rails 3, per cui aggiungiamo un riferimento a devise nel Gemfile della nostra applicazione, assicurandoci di specificare la versione corretta:

/Gemfile

gem 'devise', '1.1.rc1'

Una volta fatto ciò, lanciamo bundle per installare il gem e le sue dipendenze:

bundle install

Il prossimo passo è lanciare il generatore di installazione:

$ 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"

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

Questo comando genera una coppia di file, un initializer e un file i18n che contiene tutti i messaggi che devise può usare. Sotto quello, vengono elencati due passi da fare a mano. Il primo è di settare l’opzione host per il mailer di applicazione, mentre il secondo indica che che è necessario aver specificato l’instradamento di root. La nostra applicazione ha già questa seconda cosa impostata, per cui non dobbiamo fare altro che configurare il mailer. Lo possiamo fare copiando la linea dalle istruzioni mostrate sopra all’interno del blocco nel file di ambiente di sviluppo:

/config/environments/development.rb

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

Questa linea di codice imposta l’opzione host a localhost. Se l’applicazione andrà in produzione, si dovrebbe impostare questo valore (mutatis mutandis) nel file di configurazione ambiente corrispondente all’ambiente di produzione, ossia production.rb.

Creare un modello utente devise

Ci servirà un modello User per gestire l’autenticazione e devise fornisce proprio un generatore per fare questo. Non è necessario usarlo, ma ci risparmia un po’ di passaggi di configurazione:

$ 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

Questo generatore create un po’ di cose interessanti per noi: un file di modello, una migration e un instradamento devise_for. Ora li esamineremo uno per uno.

Il modello generato appare così:

/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

Il modello User è grossomodo simile ad ogni altro modello ActiveRecord, ma ha in particolare una chiamata al metodo devise, ed è, qui che la magia dell’autenticazione avviene. Il metodo devise accetta come argumenti una lista di moduli che vogliamo supportare nella nostra applicazione: fra questi dovreste riconoscere :rememberable e :recoverable dal momento che li abbiamo citati poco fa. Possiamo facilemente aggiungere o rimuovere moduli da questa lista per personalizzare l’autenticazione di devise per la necessità della nostra applicazione. A questo scopo abbiamo rimosso :confirmable perchè non vogliamo che gli utenti debbano confermare la loro iscrizione via email.

Si noti che la classe User ha anche un metodo attr_accessible che elenca gli attributi che l’utente può modificare mediante l’interfaccia utente. Se abbiamo delle colonne personalizzate nel nostro modello, allora possiamo aggiungerle qui.

Ora passiamo al file di migration:

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

Il file è piuttosto semplice da capire siccome c’è una chiamata a un metodo separato sulla tabella per creare le colonne necessarie per ogni modulo. Dal momento che non vogliamo usare il modulo confirmable, abbiamo commentato il metodo omonimo. Dobbiamo anche rimuovere l’indice relativo al token di conferma, perchè quella colonna non esisterà nella tabella del nostro database.

Ora che abbiamo opportunamente modificato il file di migrazione, per adattarlo ai moduli che vogliamo usare, possiamo effettivamente lanciare la migrazione:

rake db:migrate

Infine abbiamo il route devise_for che è stato aggiunto al file degli instradamenti per noi. Se lanciamo rake routes, possiamo vedere gli instradamenti che questa linea di codice crea:

    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"}

Anche se è un po’ difficile da leggere, possiamo vedere che ci sono molti instradamenti di autenticazione inclusi per il sign-in e il sign-out, il reset delle password, l’autenticazione come nuovo utente ed il reset di un profilo. Questi instradamenti sono tutti personalizzabili se volessimo cambiarli.

Possiamo ora accedere al nostro meccanismo di autenticazione mediante questi route. Se andiamo all’URL /users/sign_up vediamo la form di autenticazione come nuovo utente:

La maschera di autenticazione generata da devise.

Se riempiamo la form e clicchiamo su “Sign up”, verremo registrati e autenticati. Una volta autenticati, possiamo scollegarci andando su /users/sign_out, ma provando nuovamente a rientrare, visitando /users/sign_in e riempiendo la form con lo username e la password forniti alla registrazione, vedremo un errore:

L’autenticazione lancia un errore su Rails 3 beta 2.

Questo errore è causato da un’ anomalia di Rails 3.0 beta 2 e non dipende da devise. Se vedete questo errore, è comunque semplice ovviarlo. Nel file /config/initializers/cookie_verification_secret.rb c’è una linea di codice che imposta la chiave segreta per verificare i cookie firmati.

/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'

Tutto quello che occorre fare è rimuovere questa linea da qua e metterla in /config/application.rb, rimuovendo la sezione Rails.application iniziale:

/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

Dovremo riavviare il server per vedere le modifiche fatte, ma una volta fatto ciò, dovremmo essere in grado di accedere correttamente.

Autenticati con successo.

Ora che abbiamo imbastito un sistema di autenticazione completamente funzionante, possiamo cominciare a migliorarlo. Sarebbe carino se avessimo un link in cima alla pagina che ci ricordasse se siamo o meno autenticati nel sistema, e uno che ci permettesse di autenticarci od uscire a seconda del nostro stato.

Possiamo realizzare tutto ciò modificando il file di layout dell’applicazione in modo tale che i link siano visibili in ogni pagina. Subito prima del codice che mostra i messaggi flash, aggiungiamo le seguenti linee:

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

nel codice abbiamo un if/else statement, in modo tale da mostrare un messaggio diverso a seconda che la persona che sta visitando il sito sia autenticata su di esso o meno. Possiamo capire se lo è, chiamando il metodo user_signed_in?, che viene fornito da devise e che restituisce true se l’utente corrente è autenticato. Se c’è un utente correntemente autenticato, mostriamo l’indirizzo email con cui questo si è autenticato e un link per uscire. Possiamo mostrare l’indirizzo email chiamando current_user per avere un riferimento all’oggetto User dell’utente corrente e mostrarne la proprietà email. Per ottenere il corretto percorso per il link di disconnessione, possiamo riguardare i route generati prima con rake routes. Fra questi, c’è il destroy_user_session, che mappa l’URL relativo /users/sign_out, per cui possiamo usare destroy_user_session_path per generare l’URL corretto per il nostro link.

In modo analogo possiamo usare il new_user_registration_path ed il new_user_session_path per creare i link per la registrazione e l’autenticazione. Con questi a posto, possiamo ricaricare la pagina e vedere le informazioni utente in testata:

I dettagli dell’utente sono ora mostrati in cima ad ogni pagina.

Se clicchiamo sul link di “sign out”, vedremo comparire i link “Sign up” e “Sign in” invece:

Links to sign in or sign up are now shown when the user isn't logged in.

Come potete vedere, è piuttosto semplice mettere su un intero sistema di autenticazione da zero usando devise. Con un minimo sforzo abbiamo le funzionalità di registrazione di nuovi utenti, e quelle per permettere loro di autenticarsi e disconnettersi. Ci sono anche altre parti che non abbiamo trattato in questo episodio, come per esempio la pagina di reset della password. Se abbiamo mantenuto il modello confirmable, allora devise avrà creato per noi una form e ci avrà generato anche la logica correlata.

La pagina di reset della password.

Anche se è fantastico poter avere pressochè gratis tutte queste form di gestione delle credenziali create per noi, è probabile che vogliamo personalizzarle per adattarle all’aspetto della nostra applicazione. Per fortuna devise rende semplice anche questo aspetto, che tratteremo nel prossimo episodio.