← Todos los artículos

Invalidación de tokens en sistemas distribuidos

En esta página

Construir sistemas rápidos puede ser complicado, y la computación distribuida es a menudo el camino elegido para mejorar el rendimiento. Eso trae retos propios de seguridad, en concreto con la autenticación y la autorización. Los JSON Web Tokens de OAuth 2 lo resuelven, pero añaden riesgos a la hora de revocarlos.

Autenticación distribuida en SEEK

SEEK conoce bien el modelo de computación distribuida. Casi todo lo que ves lo sirven decenas de microservicios escritos en distintos lenguajes y mantenidos por distintos equipos.

Sidecar - a cohesive task attached to the primary application - typically placed in its own process or container - providing a homogeneous interface for services irrespective of language. (Sidecar: una tarea cohesionada que acompaña a la aplicación principal, normalmente en su propio proceso o contenedor, y que ofrece una interfaz homogénea a los servicios sea cual sea el lenguaje.)

En SEEK lo resolvimos añadiendo un sidecar junto a nuestras aplicaciones. El sidecar valida los tokens, acepta o rechaza las peticiones entrantes y pasa a la aplicación el contexto del usuario.

Para dar servicio a los muchos lenguajes que conviven en las aplicaciones de SEEK, la interfaz homogénea elegida es HTTP. Existe una implementación en casi todos los lenguajes que usamos, y las cabeceras son una buena forma de completar las peticiones con información adicional.

Otra forma de verlo es que el sidecar de autenticación funciona como un proxy.

Un ejemplo del patrón sidecar con dos conjuntos de tareas en un entorno de nube con autoescalado. En este escenario, una petición válida pasa por el sidecar hasta la tarea de la aplicación, y una petición no autorizada se corta en el sidecar. Aquí, el sidecar de autenticación usa un JSON Web Key Set (JWKS) en caché para validar la firma de los tokens. Así se pueden validar tokens de forma distribuida sin llamar al proveedor de identidad en cada petición.

Este modelo es rápido y escalable, pero por desgracia deja un riesgo: un usuario bloqueado puede seguir haciendo peticiones hasta que caduque el JSON Web Token (JWT). La especificación no recomienda un tiempo de expiración, pero lo habitual es que duren poco, unos 15 minutos.

Revocación de tokens

Parte del estándar OAuth 2 es la RFC7009, la especificación de revocación de tokens. Describe un proceso bastante básico para revocar tokens.

La especificación define un endpoint al que un cliente puede llamar para enviar el token que quiere revocar. A partir de ahí, las siguientes peticiones de validación de ese token se rechazan, porque ya se considera inválido.

Esta especificación solo funciona con tokens opacos, no con JSON Web Tokens. Este reto existe en muchísimas empresas, SEEK incluida. Nuestra arquitectura distribuida hace muy difícil comprobar los tokens de forma eficiente contra una fuente central. Por eso firmamos los tokens con una caducidad corta y aceptamos el riesgo de que un usuario bloqueado pueda seguir haciendo peticiones hasta que caduque su token.

Otro reto es contar con un mecanismo para identificar el token. Los JSON Web Tokens llevan dentro toda la información necesaria para identificar al usuario. Se empaquetan, se firman y se envían, no se guardan. Eso obliga a añadir una claim extra que identifique el token, y para eso tenemos el JWT ID.

Invalidación distribuida de tokens

Invalidar tokens en un sistema distribuido puede ser complicado, pero podemos usar un filtro de Bloom para reducir mucho el coste de comprobar los JWT ID invalidados contra una fuente central.

Los tokens suelen durar poco y, en cualquier caso, no esperaríamos tener que bloquear 100.000.000 de ellos. Caben sin problema en unos ~500MB de memoria en forma de filtro de Bloom, con una probabilidad de falso positivo de 1 entre 999.925.224.

Para resolver este escenario he estado trabajando en oauth-revokerd. El servicio mantiene una lista de JWT ID revocados y distribuye un filtro de Bloom de esa lista.

El servicio ofrece una base de datos en memoria para guardar los tokens revocados a corto plazo, hasta que caduca el propio token. La idea general es que se levante lo más rápido posible con la mínima configuración, y que ofrezca una API de gestión para revocar tokens y una API de consulta para verificar las revocaciones en caso de falsos positivos.

En este concepto, el sidecar se amplía para consumir el filtro de Bloom.

De momento, poco más

Eso es más o menos todo por ahora. Espero hacer más con este proyecto en el futuro y publicaré los avances en mi web.