homeASCIIcasts

241: Simplemente OmniAuth 

(view original Railscast)

Other translations: En Ja

Other formats:

Written by Juan Lupión

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.

The homepage of our blogging app.

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.

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

/config/initializers/omniauth.rb

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.

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

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

The routing error we get when returning from authenticating.

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.

/config/routes.rb

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.

/app/controllers/sessions_controller.rb

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.

The information in the omniauth.auth hash.

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.

/app/controllers/sessions_controller.rb

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.

/app/models/user.rb

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.

/app/controllers/sessions_controller.rb

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).

Signed in successfully.

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.

/app/controllers/application_controller.rb

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.

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

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

The user name is now shown when we're logged in.

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.

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

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

/config/routes.rb

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.

/app/controllers/sessions_controller.rb

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.

Our sign-out link now works.

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.

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

<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, .