Qué es OpenFGA y cómo modela permisos con ReBAC
Si alguna vez has escrito un if (user.role === "admin" || user.id === resource.ownerId || ...) y has sentido que cada nueva regla hacía la condición un poco más frágil, este post va de la herramienta que resuelve precisamente eso. OpenFGA es un servicio de autorización abierto, inspirado en el sistema interno que usa Google para sus permisos, y se basa en una idea sencilla: en lugar de guardar roles, guardas relaciones entre cosas, y consultas si la operación está permitida.
El problema real de los permisos en una aplicación#
Casi todas las aplicaciones empiezan igual con los permisos. Hay un campo role en la tabla de usuarios y un par de if en el código. Funciona perfectamente hasta que el producto crece. Aparece el concepto de equipo, así que ahora un permiso depende también de a qué equipo perteneces. Aparece el concepto de invitado, que es como un usuario normal pero con menos cosas. Llega el cliente que quiere compartir un recurso con alguien externo a su organización. Y el sistema de roles, que era una línea limpia, se convierte en una colección de excepciones que ya nadie entiende del todo.
La raíz del problema es que los roles son una abstracción demasiado pobre para la mayoría de las aplicaciones modernas. Un rol asume que tus usuarios se dividen en categorías estancas, cuando lo que tienes en realidad son relaciones cambiantes entre usuarios y recursos. Alguien es editor de un documento porque lo creó, no porque pertenezca a la "casta de los editores". OpenFGA parte de aceptar esto y construir el sistema sobre las relaciones, no sobre los roles.
Qué es OpenFGA exactamente#
OpenFGA, que significa Fine-Grained Authorization, es un servicio de autorización abierto, creado por Auth0/Okta, liberado en 2022 y aceptado en la CNCF en 2023. Está inspirado en Zanzibar, el sistema interno que Google publicó como paper en 2019 y que usa para resolver permisos en YouTube, Drive, Calendar y básicamente todo producto suyo donde alguien tiene que comprobar quién puede ver qué.
La promesa de OpenFGA es ser una pieza autónoma que tu aplicación consulta para decidir si una operación está permitida. Tú declaras tu modelo de autorización (los tipos de recursos que existen y las relaciones que tienen entre sí), insertas tuplas que describen el estado actual (quién es admin de qué, qué proyecto pertenece a qué equipo) y haces consultas del tipo "¿puede este usuario hacer esta acción sobre este recurso?". OpenFGA responde verdadero o falso, en milisegundos, de forma consistente, y la respuesta es la misma sea cual sea el servicio que pregunte.
RBAC, ABAC y ReBAC: qué cambia en el modelo#
Para entender por qué OpenFGA importa, conviene situarlo dentro de los tres grandes modelos de autorización que se usan habitualmente.
RBAC: Role-Based Access Control#
RBAC es el que sale por defecto. Tienes roles del estilo "admin", "editor" o "viewer", y los asignas a usuarios. Es fácil de empezar y se queda corto en cuanto hay jerarquía o recursos compartidos entre personas que no comparten rol.
ABAC: Attribute-Based Access Control#
ABAC es el modelo donde los permisos se calculan a partir de atributos (departamento, región, hora del día, sensibilidad del documento, lo que se te ocurra). Es muy expresivo y suele acabar en un motor de reglas complejo que solo entiende del todo quien lo escribió.
ReBAC: Relationship-Based Access Control#
ReBAC, que es el modelo de OpenFGA, parte de una idea distinta: lo que importa son las relaciones entre cosas. Que un usuario sea editor de un documento es una relación. Que un documento pertenezca a una carpeta es otra. Que una carpeta tenga varios padres también lo es. Las preguntas se resuelven recorriendo el grafo de relaciones, no consultando atributos sueltos ni casando contra roles.
El punto importante es que la mayoría de las reglas reales en aplicaciones grandes se expresan mejor como relaciones que como roles. "El editor de un documento puede compartirlo" es una relación. "El admin del equipo puede editar cualquier proyecto del equipo" es una relación con propagación. ReBAC modela esto sin acrobacias, porque es exactamente el lenguaje con el que se piensan los permisos en la cabeza antes de traducirlos a código.
| Modelo | Pregunta que se hace | Dónde vive la lógica | Encaja bien cuando... | Se empieza a romper cuando... |
|---|---|---|---|---|
| RBAC | ¿Qué rol tiene este usuario? | En roles globales o por scope | Hay pocos perfiles estables | El permiso depende del recurso concreto |
| ABAC | ¿Qué atributos cumplen esta regla? | En políticas y condiciones | Importan contexto, región, hora, flags o metadata | Las reglas empiezan a parecer un lenguaje propio |
| ReBAC | ¿Qué relación conecta al usuario con el recurso? | En el grafo de relaciones | Hay jerarquía, ownership, equipos o recursos compartidos | El problema es puramente contextual y no relacional |
El modelo en código#
Para ver cómo se ve un modelo de OpenFGA, voy a usar la jerarquía más típica de una aplicación SaaS: una organización (tenant) que contiene equipos (group), y dentro de cada equipo viven recursos (resource). Los actores son del tipo user.
model
schema 1.1
type user
type tenant
relations
define admin: [user]
define member: [user] or admin
type group
relations
define tenant: [tenant]
define lead: [user] or admin from tenant
define staff: [user] or lead
type resource
relations
define group: [group]
define editor: [user] or lead from group
define viewer: [user] or staff from group or editorHay tres tipos de recursos jerárquicos (tenant, group, resource) y un tipo de sujeto (user). Cada recurso declara sus relaciones, y cada relación puede ser directa (entre corchetes, asignable con una tupla) o computada (la que viene detrás de un or, calculada a partir de otra relación sin que tengas que asignarla a mano).
La línea importante es or admin from tenant. Le estás diciendo a OpenFGA que la condición de "lead de un equipo" se cumple no solo cuando alguien lo es de forma explícita, sino también cuando es admin del tenant al que pertenece el grupo. La propagación de permisos hacia abajo se declara una vez, en el modelo, y se aplica para siempre.
Las tuplas: el estado de tu plataforma#
El modelo es el esquema. Las tuplas son los datos. Cada tupla es un triplete (usuario, relación, objeto) y se va escribiendo a medida que pasan cosas en tu sistema. Para nuestro ejemplo, tres tuplas bastan:
user:01 admin tenant:01
tenant:01 tenant group:01
group:01 group resource:01Lo que estás guardando es que user:01 es admin de tenant:01, que group:01 pertenece a ese tenant y que resource:01 está dentro de ese grupo. En ningún momento has dicho que user:01 puede editar resource:01, y no hace falta. El modelo lo deriva solo, porque la regla or admin from tenant ya encadena el camino que va del tenant al grupo, y la regla or lead from group encadena el que va del grupo al recurso.
La pregunta clave: Check#
La operación más usada de OpenFGA es Check. Le pasas la tripleta de la pregunta y recibes la respuesta:
const { allowed } = await fga.check({
user: "user:01",
relation: "editor",
object: "resource:01",
});
// allowed === truePor dentro, OpenFGA no consulta una lista plana de permisos. Intenta probar la relación pedida con las tuplas del store y, si no encuentra una asignación directa, sigue las ramas computadas que declara el modelo.
Pregunta si existe
user:01 editor resource:01. No existe, así que pasa a las ramas computadas del modelo.editor = [user] or lead from group, así que necesita saber siuser:01esleaddel grupo al que perteneceresource:01.Encuentra la tupla
group:01 group resource:01, así que la pregunta se convierte enuser:01 lead group:01.lead = [user] or admin from tenant, así que necesita saber siuser:01esadmindel tenant al que pertenecegroup:01.Encuentra la tupla
tenant:01 tenant group:01, así que la pregunta se convierte enuser:01 admin tenant:01.Existe
user:01 admin tenant:01, por tanto el camino es válido y el resultado final esallowed = true.
# store · tuplas precargadas tenant:01 ├── admin: user:01 ├── group:01 │ ├── lead: user:02 │ ├── staff: user:05, user:06 │ └── resource:01 │ └── editor: user:04 └── group:02 ├── lead: user:03 ├── staff: user:07 └── resource:02 # sin tuplas user:08
# cambia usuario, relación u objeto y pulsa run
Cada check muestra el veredicto y la prueba: tuplas leídas, reglas computadas y conclusión. Prueba user:05 viewer resource:01 para ver un permiso derivado por staff from group, user:01 editor resource:02 para ver la cadena admin → lead → editor, o cualquier relación sobre user:08 para ver un caso sin camino en el grafo.
OpenFGA expone más operaciones que merece la pena conocer. ListObjects te devuelve todos los recursos sobre los que un usuario tiene cierto permiso, que es la forma correcta de filtrar listados sin tener que comprobar uno a uno. ListUsers te devuelve todos los usuarios con permiso sobre un recurso, que es exactamente lo que necesitas para pintar un panel de gestión de accesos. Y Expand te muestra el árbol de relaciones que justifica un permiso, lo cual es muy útil cuando algo no devuelve lo que esperabas y necesitas depurar.
Dónde encaja OpenFGA en tu arquitectura#
OpenFGA se monta como un servicio aparte, normalmente desplegado dentro de tu red, con su propia base de datos (Postgres o MySQL). Tu aplicación habla con él por gRPC o HTTP según la SDK que uses. El patrón habitual consiste en que el gateway o el servicio API reciba la petición ya autenticada, extraiga la identidad del usuario, llame a Check con la operación que el usuario quiere hacer, y solo si la respuesta es positiva continúe con la lógica de negocio.
La autenticación sigue siendo responsabilidad de otra pieza, ya sea un servicio de identidad propio, un IdP externo o el JWT de turno. OpenFGA solo responde a "¿puede este usuario hacer esto sobre esto otro?", no a "¿quién es este usuario?". Esa separación de responsabilidades es deliberada y deseable, porque deja cada pieza haciendo una sola cosa.
Cuándo OpenFGA tiene sentido#
Tiene sentido cuando tu modelo de permisos tiene jerarquía o recursos compartidos. Si tu aplicación necesita propagar permisos hacia abajo, como cuando un admin de equipo puede ver todos los proyectos del equipo, o hacia los lados, como cuando un documento se comparte con un usuario individual o con un equipo entero, ReBAC es el modelo natural y OpenFGA es la implementación más probada de ese modelo en código abierto.
Tiene sentido cuando trabajas con varios servicios que comparten identidad y necesitan estar de acuerdo sobre quién puede hacer qué. Centralizar la autorización en OpenFGA evita que cada servicio reimplemente la misma lógica con desviaciones sutiles que luego se traducen en bugs raros de los que cuestan días.
Tiene sentido cuando necesitas auditoría. Cada cambio en las tuplas queda registrado, y eso te da una historia de quién tenía acceso a qué en cada momento, sin que tengas que montar el sistema de auditoría tú.
Cuándo no merece la pena#
No merece la pena para una aplicación pequeña con un par de roles fijos y un equipo de tres personas. Un campo role en la tabla de usuarios y dos if en el código es la respuesta correcta hasta cierto tamaño, y cualquier cosa más sofisticada es over-engineering.
No encaja bien con reglas puramente contextuales del estilo "bloquea el acceso fuera de horario laboral" o "permite solo desde estas IPs". Existen contextual tuples para parchear casos así, pero si tu lógica es mayoritariamente ABAC vas a luchar contra la herramienta. En esos escenarios suele tener más sentido combinar OpenFGA para la parte relacional con una capa de policy aparte (algo como OPA) para las reglas contextuales.
Resumen#
OpenFGA cambia la pregunta. En vez de preguntarle a cada servicio "¿qué permisos tiene este usuario?", le preguntas a un servicio único "¿puede este usuario hacer esto?". Es un cambio que parece pequeño en la superficie, y por debajo reorganiza la forma en que piensas tus permisos, los desacopla del código de cada servicio y los mete en un sitio único, consultable y auditable. Si construyes aplicaciones con jerarquía o recursos compartidos, vale la pena al menos modelar tu sistema en el playground de OpenFGA y ver cuánto se simplifica el código que tienes hoy.
Si te interesa cómo encaja todo esto en un caso real, en otro post cuento por qué metimos OpenFGA desde el primer commit en una plataforma con muchos microservicios, y qué problemas concretos nos ahorró el haberlo decidido al inicio en lugar de migrar después.