#235 OmniAuth Part 1
- Download:
- source codeProject Files in Zip (133 KB)
- mp4Full Size H.264 Video (25.2 MB)
- m4vSmaller H.264 Video (14.6 MB)
- webmFull Size VP8 Video (30 MB)
- ogvFull Size Theora Video (32.4 MB)
Circa due settimane fa, nell’episodio 233 [guarda, leggi] ho presentato un servizio chiamto Janrain Engage, un modo centralizzato per gestire l’autenticazione tramite servizi Twitter, OpenID and Facebook. E’ un servizio fantastico ma è come se ci fosse qualcuno tra la tua applicazione e il provider di autenticazione e questo è un aspetto negativo. Una soluzione migliore potrebbe essere una gemma o un plugin che ti permetterebbe di usare nella nostra applicazione l’autenticazione fornita da terze parti senza utilizzare servizi esterni.
OnmiAuth è una gemma nuova che fornisce un’unica soluzione per connettersi a diversi servizi. Se il servizio che vuoi usare per autenticarti non è supportato da OmniAuthm, aggiungere un proprio provider è davvero semplice. OmniAuth è una una collezione di Rack middleware che da molta flessibilità su come usarla.
C’è un bel articolo nel blog di Rails Rumble blog che spiega in dettaglio come aggiungere OmniAuth ad una applicazione Rails. L’articolo mostra come creare un sistema di autenticazione da zero, ma qui spiegheremo come integrare OmniAuth in una applicazione che possiede già un sistema di autenticazione. Qui useremo Devise, si potrà adattare ad Authlogic o ad altri sistemi di autenticazione specifici facilmente.
Aggiungere OmniAuth alla nostra applicazione
Prenderemo come esempio l’episodio 209, una semplice todo-list, che usa Devise per gestire l’autenticazione degli utenti. I link “Sign up ” “Sign in ” portano l’utente ad una pagina dove si può accedere o registrarsi fornendo un nome utente e password. Inizieremo entrando con on un account già esistente in modo da renderci più semplice lo sviluppo di autenticazione tramite provider.
OmniAuth si presenta come una gemma ed è facile includerlo nella nostra applicazione modificamndo il Gemfile
.
/Gemfile
source 'http://rubygems.org' gem 'rails', '3.0.0' gem 'sqlite3-ruby', :require => 'sqlite3' gem 'devise', '1.1.3' gem 'omniauth'
Eseguiamo il bundle install
(o solo bundle
) per istallare le gemme e tutte le sue dipendenze. OmniAuth ha diverse dipendenze ma sarà il budler che si occuperà di installarle tutte.
Il prossimo passo è quello di andare nella directory dell’applicazione /config/initializers
e creare un nuovo file. Lo chiameremo omniauth.rb
ma il nome non è importante. In questo file aggiungiamo OmniAuth::Builder
al middleware della nostra applicazione e definiamo i provider che vogliamo utilizzare.
Rails.application.config.middleware.use OmniAuth::Builder do provider :twitter, 'CONSUMER_KEY', 'CONSUMER_SECRET' provider :facebook, 'APP_ID', 'APP_SECRET' provider :linked_in, 'CONSUMER_KEY', 'CONSUMER_SECRET' end
In questa applicazione noi andremo ad usare solo Twitter, anche se ci sono molti provider che si potrebbero scegliere, quindi rimuoviamo le ultime due linee. Per supportare l’autenticazione attraverso Twitter abbiamo la necessità di fare il setup della nostra applicazione andando alla sezione per gli sviluppatori per registrarci. Il form di registrazione è facile da compilare ed una volta registrata la nostra applicazione, avremo la chiave e un charset segreto che dovremo copiare nel nostro file initializer.
Rails.application.config.middleware.use OmniAuth::Builder do provider :twitter, 's3dXXXXXXXXXXXX', 'lR23XXXXXXXXXXXXXXXXXXXX' end
Se avviamo il server dell’applicazione già possiamo vedere OmniAuth in azione . Se visitiamo l’url /auth/twitter
saremo rediretti a Twitter e ci verrà chiesto se vogliamo permettere l’accesso a questa applicazione
Se clicchiamo su “Allow ” saremo rediretti alla nostra applicazione con l’URL /auth/twitter/callback
. La nostra applicazione ha bisogno di gestire questa URL che riguarda ciò che succede dopo che un utente ha effettuato l’accesso, il modo in cui gestiamo questa risposta dipende sa noi e questo rende OmniAuth molto flessibile. In questo caso creeremo una risorsa separata che la chiameremo Authentication
Potremmo creare il model a il controller sepratamente, ma per rendere le cose più semplici useremo il Nifty Scaffold generator di Ryan Bates per creare il model, la view e il controller con un solo comando. La risorsa Authentication
avrà un campo user_id
, un campo provider
che sarà valorizzato con il nome del provider, e .g. “ Twitter ” o “Facebook ” e un campo uid
nel quale verrà memorizzato l’identificativo utente del provider. Per il controller ci serviranno le action index
, create
e destroy
$ rails g nifty:scaffold authentication user_id:integer provider:string uid:string index create destroy
Dopo che si esegue questo comando, facciamo partire il migrate del database.
$ rake db:migrate
Successivamente settiamo le relazione tra i modelli User
e Authentication
. Un utente potrà autenticarsi in un un numero di modi differenti così da avere più autenticazioni.
class User < ActiveRecord::Base has_many :authentications # 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
Allo stesso modo Authentication
'belong to' User
.
class Authentication < ActiveRecord::Base belongs_to :user end
Lo scaffold ha pure generato un AuthenticationsController
che possiamo usare per gestire le risposte di OmniAuth. Mapperemo l’URL di risposta alla action create
, modificando il file di routes
ProjectManage::Application.routes.draw do |map| match '/auth/:provider/callback' => 'authentications#create' devise_for :users resources :projects resources :tasks resources :authentications root :to => 'projects#index' end
Da notare la colonna della stringa che viene matchata . Questo significa che noi possiamo fare il matching di ogni provider come parametro.
Dentro la action create
possiamo fare il fetch dei dettagli di autenticazione con una chiamata arequest.env["omniauth.auth"]
. Per ora ci limiteremo fare il render del testo in modo da vedere quali sono le informazioni contenute.
class AuthenticationsController < ApplicationController def index end def create render :text => request.env["omniauth.auth"].to_yaml end def destroy end end
Se visitiamo /auth/twitter
adesso e ci autentichiamo su Twitter avremo come risposta molte informazioni.
Le Informazioni restituite sono un hash di hash. All’inizio della lista c’è il provider
e il campo uid
che ci interessano. Conserveremo sia il nome del provider che che l’uid nel nostro modello di Authentication
. Alla fine del file c’è qualche informazione sull’utente che potrebbe essere utile salvare in User
model.
user_info: nickname: eifion name: Eifion location: North Wales image: http://a1.twimg.com/profile_images/434158309/Adium_Icon_normal.png description: Web developer using .Net and Windows by day and Ruby and Rails on OS X the rest of the time. I run http://asciicasts.com urls: Website: http://asciicasts.com
Salvare le Informazioni di Authentication
Abbiamo bisogno di modificare la action create
e cambiare il suo comportamento a seconda dello stato corrente dell’utente, ma prima vedremo l’esempio più facile. Quando un utente è attualmente connesso vogliamo solo aggiungere questa nuova autenticazione ai loro account utente.
def create auth = request.env["omniauth.auth"] current_user.authentications.create(:provider => auth['provider'], :uid => auth['uid']) flash[:notice] = "Authentication successful." redirect_to authentications_url end
Nella action create
possiamo ottenere informazioni per l’autenticazione in un hash e creare una nuova Authentication
per l’utente basata su due parametri grazie alle informazioni ritornate dal provider di autenticazione. Quindi creiamo un messaggio flash
e ritorneremo nella action index
.
Se visitiamo /auth/twitter
and ci autentichiamo, saremo rediretti nella pagina index dove potremo vedere i dettagli della nuova autenticazione appena effettuata con il provider e l’uid corretto.
Migliorare il Look della pagina Index
C’è un account molto utile su GitHub chiamato Authbuttons, che ha le icone per molti provider differenti. Possiamo usarli per migliorare il look della pagina dove si sceglie il provider di autenticazione. Per convenienza andremo a gestire tutto dentro la action index
di AuthenticationsController
ma in produzione si potrebbe spostare in una pagina separata.
Prima di fare questo abbiamo bisogni di cambiare il codice nella index
. Il codice dello scaffolding generato farà la fetch di tutte le autenticazioni. Cambiamolo in modo che venga selezionato solo l’autenticazione del current_user.
def index @authentications = current_user.authentications if current_user end
Allo stesso modo possiamo cambiare la action destroy
così che non si possono eliminare autenticazione che non belong_to
al current user.
def destroy @authentication = current_user.authentications.find(params[:id]) @authentication.destroy flash[:notice] = "Successfully destroyed authentication." redirect_to authentications_url end
Successivamente abbiamo bisogno di cambiare il codice della view, nulla di complicato solo qualche modifica.
<% title "Sign In" %> <% if @authentications %> <% unless @authentications.empty? %> <p><strong>You can sign in to this account using:</strong></p> <div class="authentications"> <% for authentication in @authentications %> <div class="authentication"> <%= image_tag "#{authentication.provider}_32.png", :size => "32x32" %> <div class="provider"><%= authentication.provider.titleize%></div> <div class="uid"><%= authentication.uid %></div> <%= link_to "X", authentication, :confirm => 'Are you sure you want to remove this authentication option?', :method => :delete, :class => "remove" %> </div> <% end %> <div class="clear"></div> </div> <% end %> <p><strong>Add another service to sign in with:</strong></p> <% else %> <p><strong>Sign in through one of these services:</strong></p> <% end %> <a href="/auth/twitter" class="auth_provider"> <%= image_tag "twitter_64.png", :size => "64x64", :alt => "Twitter" %>Twitter</a> <a href="/auth/facebook" class="auth_provider"> <%= image_tag "facebook_64.png", :size => "64x64", :alt => "Facebook" %>Facebook</a> <a href="/auth/google_apps" class="auth_provider"> <%= image_tag "google_64.png", :size => "64x64", :alt => "Google" %>Google</a> <a href="/auth/open_id" class="auth_provider"> <%= image_tag "openid_64.png", :size => "64x64", :alt => "OpenID" %>OpenID</a> <div class="clear"></div>
Se ricarichiamo la pagina di autenticazione possiamo vedere come sia migliorata nel look.
La nostra applicazione non supporta tutti i servizi mostrati negli screenshoot, ma quelli che si potrebbero supportare. Anche se questa pagina è migliorata nel look, ha un bug. Autenticandosi con Twitter proviamo a fare una seconda autenticazione sempre con Twitter, verrà creato un nuovo record già esistente in Authentication.
Il problema è abbastanza facile da risolvere, basta modificare la create
action di AuthenticationController
così da usare find_or_create_by_
per vedere se l'utente si è autenticato con quel provider prima di crearne uno nuovo record in Authentication.
def create auth = request.env["omniauth.auth"] current_user.authentications.find_or_create_by_provider_and_uid(auth['provider'], auth['uid']) flash[:notice] = "Authentication successful." redirect_to authentications_url end
Adesso se vogliamo rimuovere la seconda autenticazione e riprovare ad autenticarci sempre via Twitter avremo solo una autenticazione per utente
Una delle cosa che non abbiamo affrontato è l’autenticazione tramite provider per utenti non registrati. Se andiamo a guardare nella action create vedremo che il codice presuppone che noi abbiamo un current_user. cosa succede quando si prova ad autenticarsi via Twitter senza essere già registrati al sito? Parleremo di questo problema nel prossimo episodio.