Volver al blog
Keycloak como emisor de identidad de la plataforma

Gestión de identidad centralizada en Keycloak

LaboratorioKeycloakRBACIdentidadSeguridad

Delegar la identidad en un emisor OIDC se explica en dos frases y se implementa en bastantes más. Este post es la parte de después: qué pide la aplicación, qué le devuelve Keycloak, y cómo se traduce eso en permisos dentro de una plataforma donde cada cliente tiene su propio espacio.

El argumento de por qué conviene —y qué significa para una auditoría— está en el post de gobernanza. Aquí voy al mecanismo.

Por qué esto no se escribe a mano

Llevo tiempo con una postura firme sobre esto: si un equipo dedica tiempo de desarrollo a construir su propio registro, su propio inicio de sesión, su recuperación de contraseña y toda la parafernalia que arrastra, está perdiendo el tiempo y asumiendo un riesgo que no necesita.

No es que salga mal a la primera. Sale razonable a la primera, y a los dos años tienes código propio en el camino crítico de la autenticación —que es exactamente donde no quieres tener código propio—. Cada fallo que se publique en ese terreno es tuyo, y arreglarlo compite con lo que de verdad diferencia a tu producto.

Ceñirse al estándar cambia quién sostiene esa carga. OIDC está especificado, auditado y implementado por gente que se dedica a eso; el segundo factor, la política de contraseñas, el bloqueo por intentos y la rotación de sesiones vienen resueltos. Lo que queda por construir es lo único que nadie puede darte hecho: qué significa cada rol en tu negocio y quién puede tocar qué.

Conviene tener claro el vocabulario, porque casi todo lo que sigue se apoya en cuatro piezas:

Lo que la aplicación pide

El portal registra un proveedor OIDC contra el realm y pide tres ámbitos: identificador, correo y perfil. Nada más. No pide permisos de administración ni acceso a la API de Keycloak, porque para el inicio de sesión no hacen falta.

El intercambio es el de código de autorización: la aplicación manda al usuario al emisor, el emisor autentica y devuelve un código, la aplicación lo canjea por un juego de tokens. La contraseña no pasa por la aplicación en ningún momento, y por tanto tampoco por sus registros ni por su base de datos.

Por qué se lee el id_token y no solo userinfo

Con los tokens en la mano hay dos formas de saber quién es el usuario: llamar al endpoint de información del emisor, o decodificar el token de identidad que ya te ha dado. El portal hace lo segundo, y lo primero solo como respaldo.

La razón es que las afirmaciones que importan —los roles del realm y las organizaciones a las que pertenece— viajan dentro del token. Ir a buscarlas otra vez es una llamada de red más en el camino crítico del login, y una fuente adicional que puede fallar. Si el decodificado falla, entonces sí se consulta el endpoint; si esa también falla, el inicio de sesión continúa con lo que haya.

RBAC en dos niveles

El control de acceso es RBAC —los permisos cuelgan de roles, no de usuarios—, pero con una vuelta que en una plataforma multi-tenant no es opcional: hay roles que describen qué eres en la plataforma y roles que describen qué puedes hacer dentro de un espacio concreto, y viven en sitios distintos.

NivelRolesQuién lo decide
Plataformasuperadmin, userEl realm, en el token
Espacio de clientetenant-admin, tenant-viewer, tenant-chat-user, tenant-mcp-userLa aplicación, por pertenencia
Lo global llega del emisor; lo de cada espacio se resuelve en la aplicación.

El primer nivel llega firmado en el token: el realm dice si alguien es administrador de la plataforma. El segundo no puede venir de ahí, porque un mismo usuario pertenece a varios espacios con papeles distintos —administra el suyo y solo consulta el de un cliente para el que trabaja—. Ese nivel se resuelve por pertenencia, en la aplicación.

Los dos de producto no son decorativos: uno abre el chat y otro habilita emitir credenciales para clientes externos que consulten por protocolo. Separarlos permite dar a alguien acceso a la conversación sin darle capacidad de sacar datos por fuera de la interfaz.

Y la regla que sostiene el modelo: solo quien administra un espacio puede tocar los roles de ese espacio. No es una comprobación en la pantalla, es control de acceso en el campo — una petición directa a la API tropieza con lo mismo que el formulario.

Una organización allí, un espacio aquí

Keycloak tiene organizaciones; el portal tiene espacios de cliente. Son la misma idea en dos sistemas, y mantenerlas alineadas a mano dura hasta el primer cliente nuevo un viernes por la tarde.

Por eso la sincronización es un endpoint que se dispara desde la propia administración: pide un token de administración al realm de gestión, lista las organizaciones y crea o actualiza el espacio correspondiente. La dirección es siempre la misma —el emisor manda, el portal obedece—, así que no hay dos sitios donde dar de alta a un cliente.

// El listado devuelve diez organizaciones si no se le pide otra cosa
const url = `${keycloakUrl}/admin/realms/${realm}/organizations?first=0&max=1000`

Ese mil no es un número redondo por casualidad: es el tope que decidí para traerlo todo en una sola petición en vez de paginar. Funciona hasta que un realm tenga más organizaciones que eso, y está escrito en el código para que quien lo encuentre sepa qué tocar.

El emparejamiento no se hace por nombre, sino por el identificador que Keycloak asigna a cada organización y que el espacio guarda como referencia. Parece un detalle y evita el problema clásico: un cliente cambia de nombre comercial, alguien lo renombra en el emisor, y a la siguiente sincronización aparece un espacio duplicado en lugar de actualizarse el que ya había.

Con el identificador como clave, un cambio de nombre es una actualización. El proceso devuelve además qué creó y qué actualizó, que es lo mínimo para saber si una sincronización hizo algo o no hizo nada.

Qué se rompe si el emisor no está

Vale la pena decirlo porque es la objeción razonable a centralizar la identidad: si el emisor se cae, nadie inicia sesión. Es cierto, y es el precio.

Lo que no se cae es lo que ya está dentro. Las sesiones vivas siguen vivas, y los tokens de API que los usuarios crearon para integraciones no pasan por el emisor en cada petición: los valida la propia aplicación. Un corte del emisor impide entrar; no tumba lo que hay corriendo ni las integraciones automáticas.

Por eso el emisor va como despliegue independiente, con su ciclo de vida separado del producto. Actualizarlo no arrastra una versión de la aplicación, y un fallo al desplegar la aplicación no se lleva por delante la identidad de toda la plataforma.

Lo que no me gusta de cómo está hoy

La sincronización usa credenciales de administración con concesión por contraseña contra el realm de gestión. Funciona y es lo más directo, pero significa que la aplicación guarda una credencial que puede hacer mucho más de lo que necesita: lo correcto sería un cliente de servicio con permisos acotados a leer organizaciones.

Tampoco es automática: alguien tiene que pulsar el botón. Para el ritmo al que doy de alta clientes es suficiente, y prefiero eso a un proceso en segundo plano que falle en silencio. Cuando deje de serlo, el sitio natural es el propio inicio de sesión: si el token trae una organización que no existe como espacio, crearla en ese momento.

Seguir leyendo