JSON Web Tokens | JWT

Autenticación y gestión de la sesión en APIs

How aprenderemos sobre JSON Web Tokens. Una de las principales características de las APIs es su condición de no tener estado, o por su término en inglés: de ser stateless.

¿Qué implica esto? Pues que cada vez que se recibe una petición hay que volver a recrear el estado, incluida la sesión del usuario y su autenticación.

Puede parecer engorroso e innecesario, pero el hecho de ser stateless es la característica principal que nos permite distribuir nuestra API en numerosos servidores sin necesidad de tener las sesiones persistidas de forma distribuida, lo cual sí es un engorro innecesario.

Obviamente, lo que no podemos hacer es solicitar al usuario que se autentique con cada petición que tenga que hacer al API, ya que esto sería una experiencia de uso nefasta. Para ello la solución más común es utilizar un sistema de autenticación basado en tokens. Veamos un par de ejemplos:

Tokens JWT

Las siglas JWT provienen de JSON Web Tokens, y tal y como indica su nombre son unos tokens basados en JSON. En realidad se trata de un estándar abierto que define una forma compacta y autocontenida para transmitir de forma segura información entre backend y frontend en forma de objeto JSON. Es segura porque esta información puede ser verificada y confiada ya que viene firmada digitalmente. Los tokens JWT puede firmarse mediante el algoritmo HMAC o mediante un sistema PKI de claves públicas y privadas basadas en RSA o ECDSA.

Opcionalmente estos tokens pueden ser encriptados para ocultar la información durante la transmisión entre ambas partes. La firma del token permite verificar la integridad de la información contenida en él, y al realizarse mediante claves públicas y privadas, garantiza que solo el holder de la clave privada ha podido emitir esa información.

Veamos un ejemplo de un token JWT:

JSON Web Tokens | JWT

Como se puede observar hay una estructura de tres elementos separados por puntos, tal que así:

xxxxxxxxxx.yyyyyyyyyy.zzzzzzzzzz

Cada una de estas secciones, codificadas en base64, son respectivamente:

  • Header (Cabecera)
  • Payload (Cuerpo)
  • Signature (Firma)

Al realizar la decodificación de cada uno de estos elementos podemos ver el contenido de la cabecera y el cuerpo (la firma no va codificada):

JSON Web Tokens | JWT

Header

Habitualmente la cabecera se compone de dos elementos: el tipo (typ) que siempre en este caso es JWT, y el algoritmo que se usa para verificar la firma.

Payload

El cuerpo por su parte se compone de una serie de atributos (o claims) registrados (sub, iss, exp, iat…) privados (admin…) y públicos (name).

La lista de claims registrados puede consultarse en la especificación oficial. Usualmente se componen solo de tres letras, ya que se supone que el token debe de ser compacto. Veamos algunos de ellos:

  • iss: issuer o quién ha emitido el token.
  • sub: subject o el motivo por el que se ha emitido el token.
  • exp: expiration, la fecha en que expirará el token. Habitualmente en forma de timestamp.
  • iat: issued at, la fecha en que se emitió el token. Habitualmente en forma de timestamp.
  • aud: audience, el público a quien va dirigido el token.

Usualmente el claim más usado es el de exp, ya que en muchos casos el resto son irrelevantes, pero la fecha de caducidad siempre es importante.


Los claims privados son todos aquellos que nosotros queramos incluir: nombre, email, id… todo cuanto necesitemos.


Los claims públicos pueden ser definidos por los usuarios de JWT, pero para evitar colisiones estos deben de estar registrados en el Registro IANA de JSON Web Tokens.

Signature

Realizar la verificación de la firma es tan sencillo como utilizar la clave privada para firmar ese par de cabecera/cuerpo, y comprobar que coincide con la que nos envían para garantizar que el firmante está en posesión de la clave privada para realizar la firma.

Uso del token

Tras realizar login con el API, esta nos devuelve un token similar al que hemos visto antes, la forma de utilizar estos tokens es incluirlos con la cabecera Authorization de este modo:

Authorization: Bearer eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.jYW04zLDHfR1v7xdrW3lCGZrMIsVe0vWCfVkN2DRns2c3MN-mcp_-RE6TN9umSBYoNV-mnb31wFf8iun3fB6aDS6m_OXAiURVEKrPFNGlR38JSHUtsFzqTOj-wFrJZN4RwvZnNGSMvK3wzzUriZqmiNLsG8lktlEn6KA4kYVaM61_NpmPHWAjGExWv7cjHYupcjMSmR8uMTwN5UuAwgW6FRstCJEfoxwb0WKiyoaSlDuIiHZJ0cyGhhEmmAPiCwtPAwGeaL1yZMcp0p82cpTQ5Qb-7CtRov3N4DcOHgWYk6LomPR5j5cCkePAz87duqyzSMpCB0mCOuE3CU2VMtGeQ

La palabra clave Bearer indica que es un token autocontenido. Mientras el token sea válido (el atributo exp contiene la fecha de caducidad) el API nos dará acceso a la sesión del usuario logueado.

Refresh Tokens

Habitualmente el TTL (time to live) o duración del token, es relativamente corto, ya que cualquiera que tenga el token podría impersonar la sesión. El problema es que no podemos estar pidiéndole cada dos por tres al usuario que se autentique, por lo que tenemos que ser creativos y buscar una alternativa.

La alternativa preferida es el uso del refresh token. El refresh token es un token que nos permite obtener un nuevo token de sesión sin necesidad de volver a introducir los credenciales del usuario. Por normal general el refresh token tiene una duración muy superior a la del token de sesión, y es responsabilidad del frontend almacenarlo de la manera más segura posible.

Al contrario que los tokens de sesión, estos se guardan en base de datos y pueden invalidarse manualmente, por lo que si somos conscientes o sospechamos que hubo un robo del token, podemos invalidarlos y dejar al atacante sin posibilidad de seguir utilizándolo.

Además de esto, podemos tomar precauciones extra, como asociarlo a una IP en concreto o cualquier otra medida que nos parezca apropiada.

Basic Auth

Esta es una forma de autenticación totalmente desaconsejada, ya que implica almacenar temporalmente la contraseña del usuario para realizar la autenticación.

Básicamente consiste en realizar una codificación en base64 del nombre del usuario junto con su contraseña, separados por el carácter de los dos puntos y enviarlo en la cabecera Authorization.

Supongamos que el usuario es admin@new.secture.com y la contraseña p4ssW0rD entonces la cadena a codificar sería:

admin@new.secture.com:p4ssW0rD

Daría como resultado:

YWRtaW5Ac2VjdHVyZS5jb206cDRzc1cwckQ=

Y la usaríamos de esta manera:

Authorization: Basic YWRtaW5Ac2VjdHVyZS5jb206cDRzc1cwckQ=

En este caso la palabra clave Basic indicaría el tipo de autenticación.

Como se puede ver, es muy sencillo que un potencial atacante esnife el tráfico y obtenga la contraseña de nuestro usuario con una simple decodificación del token.

Conclusión

Al tratar con APIs sin estado necesitamos hacer uso de algún método para recrear la sesión del usuario. Actualmente existen varias alternativas. Aquí hemos visto dos, pero solo recomendamos el uso de JWT, ya que es un estándar abierto, enfocado en la seguridad y la integridad de los datos y ampliamente usado en producción.

La autenticación básica es solo un remanente de épocas pasadas que está condenado a desaparecer, ya que es totalmente inseguro.

¡Desde Secture te animamos a dejar comentarios explicando qué otros medios usas para controlar las sesiones de los usuarios de tu API!

_ Bibliografía:
JSON Web Tokens (JWT)
Basic Access Authorization

Backend

Picture of Miguel Ángel Sánchez Chordi

Miguel Ángel Sánchez Chordi

Ingeniero de software. Me encanta que los planes salgan bien.
Picture of Miguel Ángel Sánchez Chordi

Miguel Ángel Sánchez Chordi

Ingeniero de software. Me encanta que los planes salgan bien.

We are HIRING!

What Can We Do