Autenticación

Google OAuth

Diagrama del proceso de autenticación

Vamos a estudiar en detalle el proceso de autenticación de usuarios a través de Google OAuth.

Para ello, contamos con el siguiente diagrama:

Google OAuth

Como puedes ver, tenemos tres divisiones: el navegador del cliente, el servidor donde corre nuestra aplicación, y los servidores de Google.

¡Vamos a ello!

  1. User clicks 'Login'

Cuando un usuario hace click en el botón de login, le tenemos que dirigir a una ruta de nuestro servidor, por ejemplo, /auth/google.

  1. Forward user's request to Google

El servidor va a interpretar que alguien quiere autenticarse en la aplicación a través de Google, así que va a redirigir al usuario a los servidores de Google, google.com/auth*appId=123, para que se autentifique.

  1. Ask user if they grant permission

Google le va a preguntar al usuario si le quiere dar permisos a nuestra aplicación para acceder a su perfil, y le pedirá que se identifique con su cuenta de Google.

  1. User grants permission

Si el usuario da los permisos necesarios, Google le va a redirigir de vuelta a nuestro servidor, a una ruta que se conoce como callback, por ejemplo, /auth/google/callback?code=456.

Dentro de esa URL, vamos a encontrar un parámetro muy importante procedente de Google: code.

  1. Put user on hold, take the 'code' from the URL

De vuelta en nuestro servidor, éste va a coger el código que le llega de Google, mientras el usuario se queda a la espera.

  1. Send request to Google with 'code' included

El código procedente de Google significa que el usuario ha dado los permisos necesarios para que podamos acceder a sus datos, entonces, lo que tenemos que hacer es volver a enviarle dicho código a Google para obtener los datos del usuario.

  1. Google sees 'code' in URL, replies with details about this user

Google comprueba que el código que le estamos enviando es correcto, y nos responde con los datos del usuario que hemos solicitado.

  1. Get user details, create new record in database

Ya tenemos los datos que queríamos obtener del usuario, así que los vamos a guardar en la base de datos.

  1. Set user ID in cookie for this user

Finalmente, el servidor redirige al usuario de vuelta a la ruta inicial, con una cookie que indique que el usuario ya se ha identificado.

En los siguientes pasos, cuando el usuario necesite comunicarse con nuestra API para obtener algún recurso, enviará automáticamente al servidor la cookie que le identifica, y así este último sabrá que puede enviarle los recursos que ha solicitado con total seguridad.

Explicación en detalle

Entrando mucho más en detalle, estos son los diagramas del proceso de autenticación en los entornos de desarrollo y de producción:

Flujo OAuth en el entorno de desarrollo

Flujo OAuth en el entorno de producción

Passport.js

Una vez entendido el flujo de autenticación a través de Google OAuth, vamos a ver cómo se simplifica haciendo uso de una librería llamada Passport.js.

El anterior diagrama quedaría de la siguiente manera:

Passport responsibilities

Como puedes observar, gran parte del flujo es manejado por Passport. Sin embargo, antes de entrar en detalle quiero resolver dos cuestiones muy comunes que suelen dar dolor de cabeza al utilizar esta librería.

La primera de ellas es que, aunque Passport se encarga de manejar gran parte del flujo de autenticación, hay ciertos puntos en los que es necesario que añadamos un poco de código para que todo funcione correctamente.

La segunda, tiene que ver en cómo se estructura la librería. A pesar de referirnos a Passport como una sola librería, en realidad siempre que lo utilicemos vamos a tener que instalar mínimo dos librerías:

  • Passport: contiene el núcleo de la librería, y se encarga de que el proceso de autenticación funcione correctamente dentro de Express.
  • Passport Strategies: son helpers para autenticarse con un método en específico (email/password, Google, Facebook, Spotify, etc.). Aquí puedes ver todas las posibles strategies.

Es decir, siempre vamos a tener que instalar el núcleo, y además un helper por cada proveedor que queramos utilizar.

Instalar Passport.js

¡Vamos al lío!

Lo primero que tenemos que hacer es instalar estas dos librerías:

npm install -S passport passport-google-oauth20

`passport-google-oauth20`

La versión original de la strategy de Google es passport-google-oauth. La que estamos instalando nosotros lleva un 20 al final, que simplemente significa que utiliza el protocolo OAuth 2.0., a diferencia de la original que utiliza ambos.

Después, las importamos donde las vayamos a utilizar:

const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

Y por último, lo que hay que hacer es indicarle a passport que tiene que utilizar la strategy de Google para autentificar a los usuarios:

passport.use(new GoogleStrategy());

Configurar Google OAuth API

Para configurar el servicio de Google OAuth, necesitamos dos tokens procedentes de Google:

  • Client ID: se trata de un public token. Se puede hacer público.
  • Client secret: se trata de un private token. Debe ser privado.

Para obtenerlos accedemos al servicio Google APIs, y seguimos estos pasos:

  1. Creamos un nuevo proyecto.
  2. Habilitamos "Google+ API" (APIs & Services > Enable APIs and services > Google+ API).
  3. Completamos el "OAuth consent screen" (APIs & Services > Credentials > OAuth consent screen). Solo hace falta que rellenes el nombre de la aplicación.
  4. Creamos un "OAuth client ID" (APIs & Services > Credentials > Create credentials > OAuth client ID).

Añadir las credenciales a un archivo seguro

Como no queremos que estas contraseñas se suban a ningún repositorio Git, las añadimos al archivo config/keys.js, y luego este lo incluimos en nuestro .gitignore:

# config/keys.js

module.exports = {
  ...
  googleClientID: '###',
  googleClientSecret: '###',
  ...
};
# .gitignore

# Auth
keys.js

Finalmente, vamos a pasarle estas credenciales como parámetros a la strategy de Google que vimos anteriormente, con lo que quedaría así:

const keys = require('./config/keys');

passport.use(new GoogleStrategy(
  {
    clientID: keys.googleClientID,
    clientSecret: keys.googleClientSecret,
    callbackURL: '/auth/google/callback',
  },
  accessToken => {
    console.log(accessToken);
  }
));

Como puedes ver, estamos pasando dos parámetros:

  • Un objeto con las credenciales de la Google+ API y la URL de callback.
  • Una función de callback.

Callback URL

Recuerda que el callback URL que utilices debe ser válido, es decir, debe haber sido configurado anteriormente dentro de tu proyecto en Google APIs.

Utilizar Google OAuth API

Una vez realizada toda la configuración necesaria, vamos a crear una ruta para probar el servicio de autenticación de Google:

app.get(
  '/auth/google',
  passport.authenticate('google', {
    scope: ['profile', 'email']
  }),
);

Tenemos dos parámetros:

  • El primero es la ruta a la que queremos acceder en nuestro servidor.
  • El segundo es una función de Passport, que va a lanzar el servicio de autenticación que le indiquemos. En este caso, al pasarle como parámetro el string google, le estamos indicando que utilice el servicio de Google. Esto es MUY IMPORTANTE, ya que simplemente con este string, Passport sabe que debe de utilizar la strategy de Google que hemos declarado anteriormente.

Por último, añadimos un handler para manejar el callback URL procedente de Google:

app.get(
  '/auth/google/callback',
  passport.authenticate('google'),
);

Como ves, ambos handlers se parecen mucho. La diferencia es que este último va a recibir como parámetro un code, -que es lo que necesitamos para pedirle a Google los datos del usuario-, así que en este caso, Passport detectará dicho parámetro y sabrá que el usuario ya se ha autenticado.

Algunos aspectos generales sobre autenticación

Independientemente del servicio que utilicemos para autenticar a los usuarios en nuestra aplicación (email/contraseña, Google, Facebook, -psss, nunca utilices Facebook para autenticar a los usuarios 🙅🏽‍♂️-, Twitter...), hay ciertos aspectos que afectan a todos y cada uno de ellos.

Vamos a verlos uno por uno.

Cómo manejar la autenticación entre el navegador y el servidor

Aquí la pregunta que estamos planteando sería: ¿cómo sabemos que un usuario está logueado en nuestra aplicación?

Lo primero que tenemos que entender es que el protocolo HTTP no maneja el estado de la aplicación:

Sesiones HTTP

En el ejemplo, podemos observar dos peticiones HTTP, entre las cuales no existe ningún tipo de comunicación ni comparten información de ningún tipo.

Esto es lo que está pasando. Primero, el usuario envía una petición al servidor para loguearse, con las credenciales necesarias para ello, y éste le responde diciéndole: "OK, ya estás logueado". Unos minutos más tarde, el usuario vuelve a enviar una petición, pidiendo una lista de posts, pero en este caso, el servidor ya no tiene manera de saber si el usuario está logueado...

¿Cómo resolvemos este problema?

Autenticación basada en cookies

Lo que tenemos que hacer es, una vez el usuario se ha logueado, enviarle en la respuesta un identificador único (una cookie suele ser la mejor opción, pero también puede valer un token, entre otros), el cual deberá adjuntar en todas las peticiones que haga al servidor de ahí en adelante para identificarse.

Cookies

Siguiendo con el ejemplo, ahora cuando el usuario pida una lista con sus posts, enviará al servidor su identificador, y así éste dirá: "OK, eres el usuario 123, todo correcto", y le devolverá la lista de posts que ha pedido.

La autenticación basada en cookies es muy elgante y muy sencilla de implementar en el lado del navegador, ya que es éste quien se encarga de incluir automáticamente las cookies en todas las peticiones al servidor.

Cómo manejar la autenticación en el servidor

En el punto anterior, hemos visto que el servidor en un momento dado recibe una petición de login, comprueba si el usuario está logueado, y si es así, le responde con un identificador.

Pero, ¿cuál es la comprobación que tiene que hacer el servidor para confirmar que el usuario ya estaba registrado en la aplicación?

Primero, vamos a ver lo que ocurre cuando el usuario utiliza el método de usuario/contraseña:

Identificación con email y contraseña

Estos son los pasos que sigue el usuario en este caso:

  1. El usuario se registra en la aplicación con su email y contraseña, y nosotros almacenamos esta combinación en algún lugar de nuestro servidor.
  2. El usuario cierra la sesión.
  3. El usuario vuelve a loguearse con su usuario y contraseña, así que lo que hacemos nosotros para comprobar que el usuario ya se había registrado es comparar que la combinación email/contraseña existe.

Ahora que ya sabemos cómo funciona este flujo, vamos a ver lo que sucede en el caso de OAuth:

Identificación OAuth

Los pasos son los mismos que hemos visto en el otro caso, pero la diferencia es que en vez de utilizar una combinación de email/contraseña, tenemos que escoger algún campo del perfil del usuario que sea único.

Google OAuth

En el caso de Google OAuth, este identificador único que escogeríamos sería el profile.id.

¿Por qué hemos escogido el ID del usuario en vez del email?

Porque en el caso de Google, se pueden asociar varios emails a una misma cuenta, lo que implica que un email puede aparecer en diferentes cuentas. Sin embargo, sabemos que el ID es siempre único y no va a cambiar.

Entonces, teniendo en cuenta todo esto, y asumiendo que guardamos la información de los usuarios registrados en una base de datos MongoDB, el diagrama quedaría de la siguiente manera:

Propósito de la base de datos MongoDB

Básicamente, este diagrama se resume en que, cada vez que un usuario llega a nuestro servidor, vamos a mirar en la base de datos si el usuario existe. Si es así, simplemente respondemos con una cookie que le identifique; y si no es así, creamos un registro para identificarle de ahora en adelante.