Autenticación de usuarios con Twitter/OAuth en Grails

NOTA: Para ver el video tutorial (HD 1920×1080, 7:28 min) o para descargar la aplicación con el código fuente y librerías ya funcionando, ver la sección “Resolución de problemas” al final del post.

Introducción a OAuth

El api de Twitter nos permite acceder a los datos públicos de los usuarios, hacer búsquedas, leer twits y muchas cosas más. Gracias a la implementación del protocolo OAuth por parte de Twitter podemos crear aplicaciones que acceden a los datos privados de usuarios y manejar sus datos como si fueran ellos mismos, mandando twits y DM, gestionar sus listas o seguir/dejar de seguir a otros usuarios. La principal ventaja de OAuth es que los usuarios pueden dar o quitar los permisos de acceso a su cuenta a las aplicaciones en cualquier momento, sin comprometer su password.

Para entender esto vamos a poner un ejemplo práctico imaginario:

  1. Un amigo nos recomienda un nuevo cliente de Twitter con una interfaz increíble y decidimos probarlo. Para poder usar nuestra cuenta desde este nuevo cliente, se nos solicita el login y password de nuestra cuenta de Twitter. Entonces es cuando un sudor frío nos recorrer por la espalda y dudamos en dar nuestras credenciales a un tercero, por mucha confianza que nos de. Como somos precavidos y no queremos dar nuestra password a nadie, cerramos la aplicación y no la usamos. Fin de la historia.
  2. El mismo amigo nos recomienda otra vez la aplicación y nos dice “hey, ahora ya no hay que introducir el login y password, al parecer han implementado el protocolo OAuth”. Así que decidimos darle una segunda oportunidad. Al iniciar la aplicación, el nuevo cliente de Twitter nos pide permiso para usar nuestra cuenta. Para ello, nos redirige a la página de Twitter, nos autenticamos (si no lo estuviéramos antes) con la tranquilidad de que introducimos nuestro login y password en la página de Twitter, y no en la de un desconocido. Una vez autenticados, Twitter nos pregunta si deseamos dar permiso al nuevo cliente que estamos usando a nuestra cuenta. Aceptamos, Twitter nos redirige a la aplicación inicial y ya podemos usar nuestra cuenta desde el nuevo cliente.

¿Qué os parece? Como veis, el segundo caso es mucho mejor: el login y password nunca se dan a un desconocido y, en cualquier momento, podemos revocar el acceso del nuevo cliente a nuestra cuenta de Twitter.

Autenticando con OAuth

Una ventaja colateral de este sistema es que podemos aprovechar el mecanismo de aceptación de privilegios del api OAuth de Twitter para iniciar sesión en nuestra aplicación. Supón que tenemos una aplicación web con una parte privada que requiere autenticación. Es decir, que para que un usuario pueda iniciar sesión deberá registrarse (solo la primera vez, creando su propio usuario en el sistema, con su login/password, etc) y después introducir sus credenciales cada vez que quiera entrar. Pero es posible que el usuario ya tenga cuenta en Twitter y que, además, ya esté identificado en su navegador en otra pestaña, por ejemplo (aunque no es necesario). Así que sería estupendo que para usar nuestra aplicación solo tuviera que darle a un botón “Sign in with Twitter” y que el usuario ya se encontrara registrado y autenticado inmediatamente en nuestra aplicación.

¿Parece interesante no? Recordemos que los usuarios son vagos por naturaleza y que, a veces, por pereza no llegan a utilizar nuestra aplicación por el simple hecho de tener que rellenar un nuevo formulario de registro. Con este sistema de autenticación ahorramos a los usuarios a tener que crearse una cuenta en nuestro site, pensar en un nuevo login, una nueva password, etc. La primera vez que entren hacen click, aceptan el acceso a su cuenta de Twitter desde nuestra aplicación, y ya están registrados. Y la segunda vez, hacen click e inmediatamente han iniciado sesión.

Un ejemplo muy, muy simple de uso es la página de firmas de No me hagas robar, donde el botón “Firmar” simplemente recoge el avatar del usuario y lo añade a la lista de firmantes. Y un ejemplo real de una página que solo admite autenticación mediante Twitter es Twittercounter. Todas ellas usan el mismo mecanismo, así que vamos a hacerlo.

Empezamos a programar

Para ello vamos a necesitar lo siguiente (puedes hacerlo siguiendo a la vez el vídeo al final del post):

  1. Un proyecto Grails creado (cualquier versión). Si no lo tenemos, podemos crearlo con el comando grails create-app NombreProyecto (en los ejemplos, mi proyecto se llama Deleteme)
  2. El “consumer key” y el “consumer secret” de una aplicación Twitter creada por nosotros. Para esto necesitamos logarnos en http://dev.twitter.com con nuestro usuario y pinchar en “create app”.
  3. La librería Twitter4J. Es un único jar que se descarga desde http://twitter4j.org La versión utilizada en este tutorial es la 2.2.3. La copiamos dentro de la carpeta lib de nuestro proyecto Grails.

La configuración

Antes de empezar, añadimos nuestro consumer key y consumer secret en el fichero grails-app/conf/Config.groovy:

consumerKey = "m9TdD8QLJas8998gwp96AHZaw" // No usar esta valor, cambiadlo!
consumerSecret = "4G2RDJCoiYgjn7a98sduui2190Og125m1Pdk0"  // No usar este valor!

Clase de dominio Usuario

Creamos la clase de dominio Usuario, que será la que usaremos para persistir la información del usuario que se registra en nuestro site. Usaremos el comando grails create-domain-class Usuario y este contenido (cambiar el nombre del paquete):

package deleteme
class Usuario {
    static constraints = {}

    Long twitterId
    String name
    String screenName
    String profileImg
}

Esta clase no tiene login ni password, en su lugar, tiene el twitterId, identificador numérico que obtendremos únicamente cuando el usuario se ha conectado con Twitter. También guardaremos su nombre de Twitter (screenName), login (name) y su avatar (profileImg). Por supuesto, podemos añadir a esta clase todos los campos o relaciones que queramos.

Servicio de autenticación con Twitter

Después creamos el servicio que se encargará de autenticarnos con Twitter con grails create-service TwitterService y este contenido (cambiar el nombre del paquete):

package deleteme

import org.codehaus.groovy.grails.commons.*
import twitter4j.*
import twitter4j.auth.*

class TwitterService {

    static transactional = false
    static scope = 'session'

    Twitter twitter
    RequestToken requestToken

    String authenticate(String returnUrl) {
        twitter = new TwitterFactory().getInstance()
        twitter.setOAuthConsumer(ConfigurationHolder.config.consumerKey, ConfigurationHolder.config.consumerSecret)
        requestToken = twitter.getOAuthRequestToken(returnUrl)
        return requestToken.getAuthenticationURL()
    }

    User verifyCredentials(String oauth_verifier) {
        AccessToken accessToken = twitter.getOAuthAccessToken(requestToken, oauth_verifier)
        return twitter.verifyCredentials()
    }
}

Como veis, es un servicio de sesión, ya que los atributos que se crean en el método authenticate() se usan en verifyCredentials(). De hecho, deben llamarse en este orden siempre.

El controlador

A continuación creamos el controlador que se encargará de obtener el id de twitter cuando hace click en el botón “Sign in with Twitter”. El controlador tiene dos actions, uno invocado por el usuario y otro por Twitter para devolvernos el control. Para crearlo usamos el comando grails create-controller TwitterLogin y copiamos este código:

package deleteme

import twitter4j.*

class TwitterLoginController {

    def twitterService

    def index = {}

    def login = {
        redirect url: twitterService.authenticate("http://localhost:8080/Deleteme/twitterLogin/callback") // [1]
    }

    def callback = {
        if (params.denied){
            flash.message = "Permiso denegado"

        } else {
            Usuario usuario = checkTwitterUser(twitterService.verifyCredentials(params.oauth_verifier))

            session.user = usuario
        }
        redirect action: index
    }

    private checkTwitterUser(User twitterUser) {
        Usuario user = Usuario.findByTwitterId(twitterUser.id)

        if (!user) {
            user = new Usuario(twitterId: twitterUser.id)
        }

        user.name = twitterUser.name
        user.screenName = twitterUser.screenName
        user.profileImg = twitterUser.profileImageURL.toString()

        user.save()
    }

    def logout = {
        session.invalidate()
        redirect action: index
    }
}

La url de [1] debe ser accesible desde el navegador del usuario y se debe corresponder con el action callback de este mismo controlador, por lo que el host, puerto y contexto de la aplicación pueden cambiar: en mi caso es Deleteme, pero en el vuestro es el nombre del proyecto Grails que habéis usado durante su creación. El host y puerto localhost:8080 son los que usa por defecto Grails en modo desarrollo, en producción debería ser un dominio accesible desde internet válido.
El método checkTwitterUser() es el que se encarga de buscar, o si no existe de crear, el usuario en nuestra base de datos. Para ello utiliza el id del usuario de Twitter, que es simplemente un número único que nunca cambia (no como su nick, que si puede cambiar). Exista o no exista el usuario en nuestra base de datos, escribimos de nuevo los datos del perfil, pues estos pueden haber cambiado desde la última vez, y queremos tener siempre su nombre y su avatar actualizados. Este método es el que permite registrar automáticamente al usuario la primera vez que hace click.

La vista

Para acabar, creamos la vista del action index en grails-app/views/twitterLogin/index.gsp

<html>
  <body>

  <p style="color:red">${flash.message}</p>

  <div>
      <g:if test="${session.user}">
          <img src="${session.user.profileImg}"> Bienvenido <strong>${session.user.name}</strong> |
          <g:link controller="twitterLogin" action="logout">Logout</g:link>
      </g:if>
      <g:else>
            <g:link controller="twitterLogin" action="login">Signin with Twitter</g:link>
      </g:else>
  </div>
  </body>
</html>

Simplemente verifica que no haya en la sesión el usuario (se guarda en session.user) para mostrar el enlace “Signin with Twitter”. Si el usuario estuviera en la sesión, se mostraría su nombre, su avatar de Twitter y un enlace “logout” para cerrar la sesión.

Y para que nada más arrancar se muestre nuestra página de login, modificamos el mapeo de “/” en el fichero grails-app/conf/UrlMappings.groovy así:

"/"(controller:"twitterLogin", action: "index")

Probando la aplicación

Y arrancamos la aplicación con grails run-app. Hacemos click en el enlace “Signin with Twitter”, nos llevará a Twitter, aceptaremos la petición y nos devolverá el control a la aplicación. Si ahora pinchamos en logout y volvemos a repetir el proceso, veremos que es inmediato.

¿Qué hacemos a partir de aquí? pues ya sabemos que con este login tenemos una entidad de la clase Usuario en la sesión (en concreto, en session.user) y que guarda tanto el nombre como el avatar. Seguramente esta no sea la mejor práctica para guardar los datos del usuario en la sesión, pero es una manera rápida y clara de ver la aplicación funcionando. El reto más importante será permitir un login dual: que el usuario pueda autenticarse con su usuario de Twitter, como acabamos de hacer, pero que también pueda hacerlo manualmente, rellenando sus datos en un formulario de registro. Esto es útil, sobre todo, si el usuario no tiene cuenta de Twitter para no obligarle a crearse una. La autenticación dual es interesante porque puede producirse en cualquier orden, pero eso lo veremos otro día.

Espero que os haya gustado y que os sirva para vuestras aplicaciones.

Resolución de problemas

Puedes ver el screencast de 7:28 minutos en HD siguiendo este tutorial paso a paso aquí:

Enlace directo al vídeo en Vimeo en el Groovy TV channel.

Y también puedes descargarte la aplicación ya finalizada y completamente funcional. Incluye el código fuente y la librería Twitter4J 2.2.3. Las instrucciones de instalación son:

  1. Descargar y decomprimir el archivo TwitterOAuth.zip (452Kb)
  2. Añadir el “consumer key” y el “consumer secret” de una aplicación Twitter creada por nosotros en el fichero grails-app/conf/Config.groovy (al final del todo). Para esto necesitamos logarnos en http://dev.twitter.com con nuestro usuario y pinchar en “create app” (ver el vídeo).
  3. Arrancar la aplicación con grails run-app.