#209 Introducing Devise
- Download:
- source codeProject Files in Zip (156 KB)
- mp4Full Size H.264 Video (20.9 MB)
- m4vSmaller H.264 Video (12.6 MB)
- webmFull Size VP8 Video (30.8 MB)
- ogvFull Size Theora Video (28.7 MB)
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.
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:
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.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ì:
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:
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:
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.
# 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:
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.
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:
<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:
Se clicchiamo sul link di “sign out”, vedremo comparire i link “Sign up” e “Sign in” invece:
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.
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.