#241 Simple OmniAuth
- Download:
- source codeProject Files in Zip (101 KB)
- mp4Full Size H.264 Video (13.5 MB)
- m4vSmaller H.264 Video (9.23 MB)
- webmFull Size VP8 Video (23.8 MB)
- ogvFull Size Theora Video (18.5 MB)
Vimos OmniAuth hace unas semanas en los episodios 235 [verlo, leerlo] y 236 [verlo, leerlo]. OmniAuth viene muy bien para integrar servicios de autenticación de terceros como Twitter y Facebook en nuestras aplicaciones Rails. El escenario que se presentaba en dichos episodios era bastante complejo porque estábamos integrando OmniAuth sobre una aplicación que ya tenía implementado un sistema de autenticación por clave de usuario basado en Devise. También estábamos gestionando múltiples autenticaciones por usuario. En este episodio veremos que si podemos prescindir de todos estos requisitos extra podremos realizar la autenticación con OmniAuth de forma mucho más simple.
La aplicación que usaremos en este episodio es el sencillo blog que hemos visto anterioremente. Ahora mismo no dispone de autenticación, así que la añadiremos via Twitter usando sólamente OmniAuth. No utilizaremos Authlogic, Devise o ningún otro tipo de solución de autenticación, y así veremos lo simple que es OmniAuth en realidad.
Lo primero es añadir la dependencia de la gema de OmniAuth. Nuestra aplicación está escrita en Rails 3 por lo que lo añadiremos a nuestro Gemfile
.
gem "omniauth"
No debemos olvidar ejecutar bundler
para asegurarnos de que todas las gemas necesarias están instaladas. A continuación crearemos un fichero de inicialización llamado omniauth.rb
. En este fichero añadiremos el código necesario para cargar OmniAuth y configurarlo para usar Twitter.
Rails.application.config.middleware.use OmniAuth::Builder do provider :twitter, 'CONSUMER_KEY', 'CONSUMER_SECRET' end
Obsérvese que hemos añadido OmniAuth a nuestra aplicación como middleware. OmniAuth es simplemente un middleware de Rack, lo que es muy útil porque significa que está desacoplado del resto de nuestra aplicación, y así podemos implementar la autenticación en nuestra aplicación como queramos.
Unicamente tenemos un proveedor de autenticación: Twitter. Tenemos que cambiar los valores de CONSUMER_KEY
y <coce>CONSUMER_SECRET</coce> en el código anteiror con valores reales. Podemos conseguirlos entrando en la página para desarroladores en Twitter y registrando nuestra aplicación. Una vez que hayamos hecho esto podemos comenzar a añadir la autenticación en nuestra aplicación.
En el fichero de layout de nuestra aplicación mostramos un texto por defecto que queremos que sea reemplazado por un enlace de inicio de sesión. Este texto llevará a la URL para Twitter de OmniAuth que será /auth/twitter
.
<div id="user_nav"> <%= link_to "Sign in with Twitter", "/auth/twitter" %> </div>
La aplicación ahora tiene un enlace de inicio de sesión que, cuando hagamos clic en él, nos redirigirá a la web de Twitter donde se nos consultará si queremos dar acceso a la aplicación. Si se pulsa el botón ‘allow’ seremos redirigidos de vuelta a nuestra aplicación donde veremos un error de rutas.
Este error se debe a que el navegador es dirigido de vuelta a una URL dispuesta por OmniAuth después de recibir la autenticación satisfactoriamente, y debemos implementar la entrada correspondiente en nuestro fichero de rutas. Para gestionar la autenticación vamos a crear un nuevo SessionsController
y haremos que la ruta se corresponda con su acción create
.
Blog::Application.routes.draw do |map| root :to => "articles#index" match "/auth/:provider/callback" => "sessions#create" resources :comments resources :articles end
Nótese que hemos cambiado la parte de twitter
en la URL por un parámetro :provider
para que la ruta funcione con cualquier otro proveedor de autenticación que queramos implementar.
Lo siguiente será generar el controlador propiamente dicho.
$ rails g controller sessions
Cuando la aplicación vuelva de la autenticación existe una variable de entorno llamada omniauth.auth
que contiene un diccionario con información acerca del proceso de autenticación. Por ahora simplemente mostraremos esta información elevando una excepción.
class SessionsController < ApplicationController def create raise request.env["omniauth.auth"].to_yaml end end
Si ahora intentamos iniciar la sesión se nos mostrará esta información cuando seamos redirigidos de vuelta a la aplicación.
Podemos usar la información de este hash bien para iniciar la sesión de un usuario existente o crear uno nuevo si no tenemos una cuenta ya creada. Tendremos que generar un modelo User
porque la aplicación todavía no tiene ninguno.
$ rails g model user provider:string uid:string name:string
Es importante que en este modelo existan las columnas provider
y uid
porque serán los campos que identificarán a este usuario cuando inicie la sesión. Le daremos también al modelo una columna name
. A continuación migraremos la base de datos.
$ rake db:migrate
Ya podemos volver al controlador SessionsController
y utilizar la información que nos pasa OmniAuth para recuperar o crear a un nuevo usuario.
class SessionsController < ApplicationController def create auth = request.env["omniauth.auth"] user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth) end end
En el método create
tratamos de encontrar un usuario que tenga los valores provider
y uid
recibidos del hash de OmniAuth. Si no encontramos ninguno, invocaremos al método de clase create_with_omniauth
en el modelo User
, que pasamos a escribir.
class User < ActiveRecord::Base def self.create_with_omniauth(auth) create! do |user| user.provider = auth["provider"] user.uid = auth["uid"] user.name = auth["user_info"]["name"] end end end
En el método anterior le pasamos un bloque a create!
. Esto es útil porque nos permite modificar el nuevo usuario antes de guardarlo en la base de datos y porque también devuelve la instancia recién creada. Con este método escrito podemos volver al controlador y terminar de escribir el método create
. Guardaremos el id
del usuario en la sesión y luego redirigiremos a la página principal y mostraremos un mensaje de bienvenida.
class SessionsController < ApplicationController def create auth = request.env["omniauth.auth"] user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth) session[:user_id] = user.id redirect_to root_url, :notice => "Signed in!" end end
Si ahora hacemos clic en el enlace “Sign in with Twitter” se iniciará la sesión automáticamente (asumiendo que sigamos estando logados en la misma cuenta de Twitter).
Tenemos, sin embargo, un problema con la página. Aunque hemos iniciado la sesión con el enlace de arriba, éste aún sigue diciendo “sign in”. En su lugar, debería mostrar el nombrel del usuario con sesión iniciada y un enlace para cerrar la sesión.
Tenemos que ser capaces de acceder al usuario en la sesión por lo que añadiremos un método current_user
en la clase ApplicationController
. Este método devolverá el usuario actual basándose en el user_id
almacenado en la sesión y cacheará el resultado en una variable de instancia. Haremos que sea un método helper al que se pueda acceder desde vistas y controladores.
class ApplicationController < ActionController::Base protect_from_forgery helper_method :current_user private def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] end end
Ya podemos modificar el fichero de layout para que el enlace de inicio de sesión sólo aparezca cuando no tengamos sesión iniciada, en cuyo caso mostraremos el nombre del usuario.
<div id="user_nav"> <% if current_user %> Welcome, <%= current_user.name %>! <% else %> <%= link_to "Sign in with Twitter", "/auth/twitter" %> <% end %> </div>
Si ahora recargamos la página veremos el mensaje “Welcome, Eifion!” en la parte superior, porque es el usuario con el que hemos iniciado sesión en Twitter.
Sería útil mostrar un enlace para cerrar la sesión, por lo que lo mostraremos después del nombre del usuario, con un enlace a signout_path
que todavía no tenemos.
<div id="user_nav"> <% if current_user %> Welcome, <%= current_user.name %>! <%= link_to "Sign Out", signout_path %> <% else %> <%= link_to "Sign in with Twitter", "/auth/twitter" %> <% end %> </div>
A continuación mapearemos signout_path
a la acción destroy
de SessionController
.
match "/signout" => "sessions#destroy", :as => :signout
Si añadimos :as => :signout
a la ruta podremos referenciarla por signout_path
. Escribamos ahora la acción destroy
.
def destroy session[:user_id] = nil redirect_to root_url, :notice => "Signed out!" end
Si ahora recargamos la página veremos el enlace para serrar sesión, lo que ocurrirá si hacemos clic en él.
Otros proveedores
Es fácil añadir otros servicios de autenticación, precisamente esa es la ventaja de OmniAuth. Por ejemplo, para añadir la autenticación mediante Facebook tan sólo tenemos que añadir un nuevo enlace en el layout de la página.
<div id="user_nav"> <% if current_user %> Welcome, <%= current_user.name %>! <%= link_to "Sign Out", signout_path %> <% else %> <%= link_to "Sign in with Twitter", "/auth/twitter" %> <%= link_to "Sign in with Facebook", "/auth/facebook" %> <% end %> </div>
Tendremos que añadir las credenciales apropiadas en el fichero omniauth.rb
para inicializar OmniAuth y sólo con eso nuestra aplicación soportará el inicio de sesión mediante Facebook. Podríamos añadir OpenId, LinkedIn, o cualquier otro de los proveedores soportados.
Esta solución funciona muy bien y es fácil de añadir a cualquier aplicación, siempre y cuando no nos haga falta una autenticación tradicional basada en usuario y clave y tampoco necesitemos añadir diferentes tipos de autenticación por usuario, .