Skip to Content

API de Autorización Detallada (FGA)

El motor de Autorización Detallada proporciona control de acceso basado en relaciones (ReBAC) al estilo Zanzibar. Extiende la capa RBAC con autorización a nivel de objeto: en lugar de “el usuario tiene el permiso X globalmente”, FGA responde “¿tiene el usuario Alice la relación viewer sobre el documento readme?”

El sistema se construye alrededor de tres conceptos:

  1. Los Modelos de Autorización definen tipos de objetos, sus relaciones y reglas de reescritura usando un DSL compatible con OpenFGA.
  2. Las Tuplas de Relaciones son los hechos del sistema. Cada tupla afirma que un sujeto tiene una relación con un objeto.
  3. Las Consultas de Autorización (check, expand, list-objects) evalúan tuplas contra las reglas de reescritura del modelo para responder preguntas de acceso.

Todos los endpoints FGA requieren el encabezado x-tenant y un token de acceso válido.

DSL del Modelo de Autorización

El DSL define tipos y sus relaciones. Cada relación puede tener reglas de reescritura que componen el acceso a partir de otras relaciones o relaciones indirectas.

type user type group relations define member: [user] type document relations define owner: [user] define editor: [user, group#member] define viewer: [user, group#member] or editor or owner

Tipos de reglas de reescritura:

ReglaSintaxisDescripción
Directa (this)[user]El sujeto debe ser asignado directamente mediante una tupla
UniónA or BEl sujeto debe satisfacer al menos una de las relaciones
IntersecciónA and BEl sujeto debe satisfacer todas las relaciones
ExclusiónA but not BEl sujeto debe satisfacer A y no debe satisfacer B
Conjunto de usuarios calculadoownerHereda de otra relación en el mismo objeto
Tupla a conjunto de usuariosgroup#memberSigue una relación en un objeto relacionado (indirecto)

El motor FGA usa evaluación recursiva con una profundidad máxima de 25 y detección de ciclos mediante un conjunto de visitados. Esto previene bucles infinitos en definiciones de relaciones circulares.

Modelos de Autorización

Listar Modelos

GET/api/fga/modelsRequires: view:fga_models

Lista todos los modelos de autorización del tenant, ordenados por versión descendente. Solo un modelo puede estar activo a la vez. El modelo activo se usa para toda la validación de tuplas y consultas de autorización.

Parámetros de consulta

ParámetroTipoDescripción
pageintegerNúmero de página (por defecto: 1)
limitintegerElementos por página (por defecto: 20)

Respuesta exitosa

{ "ok": true, "data": [ { "id": "model_abc123", "name": "Modelo de Autorización SaaS", "version": 3, "isActive": true, "createdAt": "2025-02-10T00:00:00Z", "updatedAt": "2025-02-12T08:30:00Z" }, { "id": "model_def456", "name": "Modelo de Autorización SaaS", "version": 2, "isActive": false, "createdAt": "2025-02-05T00:00:00Z", "updatedAt": "2025-02-05T00:00:00Z" } ] }

Crear Modelo

POST/api/fga/modelsRequires: manage:fga_models

Crea un nuevo modelo de autorización proporcionando un nombre y una definición DSL. El DSL se analiza línea por línea y se valida antes del almacenamiento. Si el DSL contiene errores de sintaxis o referencias a tipos o relaciones no definidos, la solicitud es rechazada con un mensaje de error detallado que incluye el número de línea.

Cuerpo de la solicitud

{ "name": "Modelo de Acceso a Documentos", "dsl": "type user\n\ntype group\n relations\n define member: [user]\n\ntype document\n relations\n define owner: [user]\n define editor: [user, group#member]\n define viewer: [user, group#member] or editor or owner\n" }

Respuesta exitosa

{ "ok": true, "data": { "id": "model_ghi789", "name": "Modelo de Acceso a Documentos", "version": 1, "isActive": false, "dsl": "type user\n\ntype group\n relations\n define member: [user]\n\ntype document\n relations\n define owner: [user]\n define editor: [user, group#member]\n define viewer: [user, group#member] or editor or owner\n", "schema": { "typeDefinitions": [ { "type": "user", "relations": {} }, { "type": "group", "relations": { "member": { "this": {} } } }, { "type": "document", "relations": { "owner": { "this": {} }, "editor": { "this": {} }, "viewer": { "union": { "children": [ { "this": {} }, { "computedUserset": { "relation": "editor" } }, { "computedUserset": { "relation": "owner" } } ] } } } } ] }, "createdAt": "2025-02-18T10:00:00Z" } }

Códigos de error

CódigoHTTPDescripción
DSL_PARSE_ERROR400La sintaxis del DSL es inválida. El campo message incluye el número de línea y la descripción del error
DSL_VALIDATION_ERROR400El DSL es sintácticamente válido pero referencia tipos, relaciones no definidos o contiene ciclos
VALIDATION_ERROR400Faltan campos requeridos (name o dsl)

Ejemplo de error de análisis DSL

{ "ok": false, "error": { "code": "DSL_PARSE_ERROR", "message": "Line 7: Unknown keyword 'defines'. Did you mean 'define'?" } }

Obtener Modelo

GET/api/fga/models/[id]Requires: view:fga_models

Recupera un modelo de autorización específico por su ID. Devuelve el modelo completo incluyendo el texto DSL sin procesar y el objeto de esquema analizado.

Respuesta exitosa

{ "ok": true, "data": { "id": "model_ghi789", "name": "Modelo de Acceso a Documentos", "version": 1, "isActive": false, "dsl": "type user\n\ntype group\n relations\n define member: [user]\n...", "schema": { "typeDefinitions": [ { "type": "user", "relations": {} }, { "type": "group", "relations": { "member": { "this": {} } } }, { "type": "document", "relations": { "owner": { "this": {} }, "editor": { "this": {} }, "viewer": { "union": {} } } } ] }, "createdAt": "2025-02-18T10:00:00Z", "updatedAt": "2025-02-18T10:00:00Z" } }

Códigos de error

CódigoHTTPDescripción
NOT_FOUND404El modelo no existe o pertenece a un tenant diferente

Actualizar Modelo

PUT/api/fga/models/[id]Requires: manage:fga_models

Actualiza el nombre o la definición DSL de un modelo. Cuando se actualiza el DSL, se vuelve a analizar y validar. La versión del modelo no se incrementa automáticamente en la actualización — crea un nuevo modelo para cambios versionados.

Cuerpo de la solicitud

{ "name": "Modelo de Acceso a Documentos v2", "dsl": "type user\n\ntype document\n relations\n define owner: [user]\n define viewer: [user] or owner\n" }

Tanto name como dsl son opcionales. Solo se actualizan los campos proporcionados.

Respuesta exitosa

{ "ok": true, "data": { "id": "model_ghi789", "name": "Modelo de Acceso a Documentos v2", "version": 1, "isActive": false, "schema": { "typeDefinitions": [] }, "updatedAt": "2025-02-18T11:00:00Z" } }

Códigos de error

CódigoHTTPDescripción
NOT_FOUND404El modelo no existe
DSL_PARSE_ERROR400El DSL actualizado tiene errores de sintaxis
DSL_VALIDATION_ERROR400El DSL actualizado referencia tipos o relaciones no definidos

Eliminar Modelo

DELETE/api/fga/models/[id]Requires: manage:fga_models

Elimina un modelo de autorización. Los modelos activos no pueden eliminarse — debes activar primero un modelo diferente, o desactivar activando otro modelo.

Eliminar un modelo no elimina automáticamente las tuplas escritas contra él. Las tuplas huérfanas son ignoradas por las consultas de autorización pero permanecen en la base de datos hasta que se limpien manualmente.

Respuesta exitosa

{ "ok": true, "data": { "deleted": true } }

Códigos de error

CódigoHTTPDescripción
NOT_FOUND404El modelo no existe
MODEL_IS_ACTIVE400No se puede eliminar el modelo activo actualmente

Activar Modelo

POST/api/fga/models/[id]/activateRequires: manage:fga_models

Establece un modelo como el modelo de autorización activo para el tenant. Cualquier modelo previamente activo se desactiva automáticamente. Todas las consultas check, expand y list-objects posteriores usarán las reglas de reescritura del modelo recién activado. La caché del modelo se invalida inmediatamente.

Solicitud: No se requiere cuerpo.

Respuesta exitosa

{ "ok": true, "data": { "activated": true, "modelId": "model_ghi789" } }

Códigos de error

CódigoHTTPDescripción
NOT_FOUND404El modelo no existe
ALREADY_ACTIVE400Este modelo ya es el modelo activo

El motor FGA almacena en caché el modelo activo en memoria con un TTL de 5 minutos. Después de activar un nuevo modelo, las consultas pueden usar el modelo anterior hasta 5 minutos en otras instancias del servidor. La actualización forzada ocurre inmediatamente en la instancia que procesó la solicitud de activación.

Tuplas de Relaciones

Las tuplas son los hechos de autorización. Cada tupla establece que un sujeto tiene una relación con un objeto.

Formato de tupla: tipoObjeto:idObjeto#relación@tipoSujeto:idSujeto

Ejemplos:

  • document:readme#viewer@user:alice — Alice es un viewer del documento “readme”
  • document:readme#editor@group:engineering#member — los miembros del grupo “engineering” son editores del documento “readme”
  • folder:projects#owner@user:bob — Bob es el propietario de la carpeta “projects”

Listar Tuplas

GET/api/fga/tuplesRequires: view:fga_tuples

Lista las tuplas de relaciones con filtrado opcional. Se recomienda al menos un parámetro de filtro para evitar devolver todo el almacén de tuplas. Soporta paginación.

Parámetros de consulta

ParámetroTipoDescripción
objectTypestringFiltrar por tipo de objeto (p. ej., document)
objectIdstringFiltrar por ID de objeto
relationstringFiltrar por nombre de relación
subjectTypestringFiltrar por tipo de sujeto
subjectIdstringFiltrar por ID de sujeto
pageintegerNúmero de página (por defecto: 1)
limitintegerElementos por página (por defecto: 20, máx.: 100)

Respuesta exitosa

{ "ok": true, "data": { "data": [ { "id": "tuple_abc123", "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice", "subjectRelation": null, "createdAt": "2025-02-15T10:00:00Z" }, { "id": "tuple_def456", "objectType": "document", "objectId": "readme", "relation": "editor", "subjectType": "group", "subjectId": "engineering", "subjectRelation": "member", "createdAt": "2025-02-15T10:05:00Z" } ], "pagination": { "page": 1, "limit": 20, "total": 2, "totalPages": 1 } } }

Escribir Tupla

POST/api/fga/tuplesRequires: manage:fga_tuples

Escribe una sola tupla de relación. La tupla se valida contra el modelo de autorización activo antes del almacenamiento. El tipo de objeto, la relación y el tipo de sujeto deben existir en el modelo activo, y la relación debe aceptar el tipo de sujeto como destino de asignación válido.

Cuerpo de la solicitud — asignación directa de usuario

{ "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice" }

Cuerpo de la solicitud — conjunto de sujetos (indirecto/membresía de grupo)

Cuando el sujeto no es un usuario individual sino un conjunto de usuarios definido por una relación en otro objeto, incluye subjectRelation:

{ "objectType": "document", "objectId": "readme", "relation": "editor", "subjectType": "group", "subjectId": "engineering", "subjectRelation": "member" }

Esto significa: “todas las entidades que tienen la relación member en group:engineering también son editor en document:readme.”

Respuesta exitosa

{ "ok": true, "data": { "id": "tuple_ghi789", "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice", "subjectRelation": null, "createdAt": "2025-02-18T10:00:00Z" } }

Códigos de error

CódigoHTTPDescripción
NO_ACTIVE_MODEL400No existe modelo de autorización activo para este tenant
INVALID_TYPE400El tipo de objeto no existe en el modelo activo
INVALID_RELATION400La relación no existe en este tipo de objeto en el modelo activo
INVALID_SUBJECT_TYPE400La relación no acepta este tipo de sujeto como destino válido
TUPLE_EXISTS409Ya existe una tupla idéntica
VALIDATION_ERROR400Faltan campos requeridos

Eliminar Tupla

DELETE/api/fga/tuplesRequires: manage:fga_tuples

Elimina una tupla de relación específica proporcionando los datos de la tupla en el cuerpo de la solicitud. Todos los campos de la tupla deben coincidir exactamente. Devuelve éxito incluso si la tupla no existe (eliminación idempotente).

Cuerpo de la solicitud

{ "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice" }

Respuesta exitosa

{ "ok": true, "data": { "deleted": true } }

Escritura/Eliminación Masiva de Tuplas

POST/api/fga/tuples/bulkRequires: manage:fga_tuples

Escribe y/o elimina múltiples tuplas en una sola solicitud atómica. Todas las operaciones del lote se validan contra el modelo activo antes de que ocurra cualquier escritura. Si alguna tupla individual falla la validación, todo el lote es rechazado y no se realizan cambios.

Cuerpo de la solicitud

{ "writes": [ { "objectType": "document", "objectId": "api-spec", "relation": "owner", "subjectType": "user", "subjectId": "bob" }, { "objectType": "document", "objectId": "api-spec", "relation": "viewer", "subjectType": "group", "subjectId": "engineering", "subjectRelation": "member" } ], "deletes": [ { "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice" } ] }

Tanto writes como deletes son opcionales, pero al menos uno debe estar presente. Cada array puede contener hasta 100 tuplas.

Respuesta exitosa

{ "ok": true, "data": { "written": 2, "deleted": 1 } }

Códigos de error

CódigoHTTPDescripción
NO_ACTIVE_MODEL400No existe modelo de autorización activo
INVALID_RELATION400Una tupla referencia una relación que no existe en el modelo activo
INVALID_TYPE400Una tupla referencia un tipo de objeto que no está en el modelo activo
BATCH_TOO_LARGE400El total de escrituras + eliminaciones supera el tamaño máximo del lote
VALIDATION_ERROR400Una o más tuplas tienen campos requeridos faltantes

Las operaciones masivas son atómicas. Si la tupla #47 de 100 falla la validación, ninguna de las 100 tuplas se escribe o elimina. Comprueba la respuesta de error para encontrar la tupla que falló específicamente.

Consultas de Autorización

Check

POST/api/fga/checkRequires: debug:fga

Verifica si un sujeto tiene una relación específica con un objeto. El motor evalúa las reglas de reescritura completas del modelo activo de forma recursiva, siguiendo conjuntos de usuarios calculados, indirecciones de tupla a conjunto de usuarios, uniones, intersecciones y exclusiones. Opcionalmente devuelve un árbol de resolución que muestra exactamente cómo se llegó a la decisión.

Cuerpo de la solicitud

{ "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice", "explain": false }
CampoTipoRequeridoDescripción
objectTypestringSíEl tipo del objeto objetivo
objectIdstringSíEl ID del objeto objetivo
relationstringSíLa relación a verificar
subjectTypestringSíEl tipo del sujeto (normalmente user)
subjectIdstringSíEl ID del sujeto
explainbooleanNoCuando es true, incluye el árbol de resolución (por defecto: false)

Respuesta exitosa (sin explain)

{ "ok": true, "data": { "allowed": true } }

Respuesta exitosa (con explain)

{ "ok": true, "data": { "allowed": true, "resolution": { "type": "union", "relation": "viewer", "result": true, "children": [ { "type": "this", "relation": "viewer", "result": false }, { "type": "computedUserset", "relation": "editor", "result": false }, { "type": "computedUserset", "relation": "owner", "result": true, "children": [ { "type": "this", "relation": "owner", "result": true, "tupleFound": "document:readme#owner@user:alice" } ] } ] } } }

El árbol de resolución anterior muestra: Alice no es un viewer directo ni un editor, pero sí es owner, y la relación viewer incluye or owner, por lo que la verificación es exitosa.

Códigos de error

CódigoHTTPDescripción
NO_ACTIVE_MODEL400No existe modelo de autorización activo
INVALID_TYPE400El tipo de objeto no existe en el modelo activo
INVALID_RELATION400La relación no existe en el tipo especificado
MAX_DEPTH_EXCEEDED400La evaluación superó la profundidad máxima de recursión de 25
VALIDATION_ERROR400Faltan campos requeridos

En producción, los servidores de recursos deben llamar al endpoint de check usando un token M2M con debug:fga en lugar de exponerlo a los usuarios finales. El modo explain es particularmente útil durante el desarrollo y la depuración pero añade sobrecarga y debe deshabilitarse en rutas críticas.

Expand

POST/api/fga/expandRequires: debug:fga

Expande una relación en un objeto para descubrir todos los sujetos que tienen esa relación. Devuelve una estructura de árbol que sigue las reglas de reescritura, mostrando tanto asignaciones directas de tuplas como relaciones indirectas (conjuntos de usuarios calculados, tupla a conjunto de usuarios).

Cuerpo de la solicitud

{ "objectType": "document", "objectId": "readme", "relation": "viewer" }
CampoTipoRequeridoDescripción
objectTypestringSíEl tipo del objeto objetivo
objectIdstringSíEl ID del objeto objetivo
relationstringSíLa relación a expandir

Respuesta exitosa

{ "ok": true, "data": { "tree": { "root": { "type": "union", "relation": "viewer", "nodes": [ { "type": "leaf", "relation": "viewer", "subjects": [ { "type": "user", "id": "dave" }, { "type": "user", "id": "eve" } ] }, { "type": "computedUserset", "relation": "editor", "subjects": [ { "type": "user", "id": "bob" } ] }, { "type": "computedUserset", "relation": "owner", "subjects": [ { "type": "user", "id": "alice" } ] } ] } } } }

Esto muestra que document:readme tiene los siguientes viewers: Dave y Eve (directamente), Bob (vía editor) y Alice (vía owner).

Códigos de error

CódigoHTTPDescripción
NO_ACTIVE_MODEL400No existe modelo de autorización activo
INVALID_TYPE400El tipo de objeto no fue encontrado en el modelo
INVALID_RELATION400La relación no fue encontrada en este tipo

List Objects

POST/api/fga/list-objectsRequires: debug:fga

Lista todos los objetos de un tipo dado a los que un sujeto puede acceder a través de una relación específica. Realiza una búsqueda inversa a través del almacén de tuplas y las reglas de reescritura. Útil para construir vistas filtradas como “muéstrame todos los documentos que este usuario puede ver”.

Cuerpo de la solicitud

{ "objectType": "document", "relation": "viewer", "subjectType": "user", "subjectId": "alice" }
CampoTipoRequeridoDescripción
objectTypestringSíEl tipo de objetos a buscar
relationstringSíLa relación que el sujeto debe tener
subjectTypestringSíEl tipo del sujeto
subjectIdstringSíEl ID del sujeto
pageintegerNoNúmero de página para paginación (por defecto: 1)
limitintegerNoElementos por página (por defecto: 100, máx.: 1000)

Respuesta exitosa

{ "ok": true, "data": { "objectIds": ["readme", "api-spec", "changelog", "roadmap"], "total": 4 } }

Códigos de error

CódigoHTTPDescripción
NO_ACTIVE_MODEL400No existe modelo de autorización activo
INVALID_TYPE400El tipo de objeto no fue encontrado en el modelo
INVALID_RELATION400La relación no fue encontrada en este tipo

La consulta list-objects puede ser costosa para grandes almacenes de tuplas ya que requiere escanear y evaluar múltiples tuplas. Usa paginación y considera cachear los resultados para patrones accedidos frecuentemente.

Referencia de Permisos

PermisoDescripción
view:fga_modelsVer modelos de autorización y sus definiciones DSL
manage:fga_modelsCrear, actualizar, eliminar y activar modelos de autorización
view:fga_tuplesListar y leer tuplas de relaciones
manage:fga_tuplesEscribir y eliminar tuplas de relaciones (individuales y masivas)
debug:fgaEjecutar consultas check, expand y list-objects

Ejemplo Completo

Este tutorial demuestra un patrón de autorización común: un sistema de acceso a documentos basado en equipos.

Paso 1: Crear el modelo

POST /api/fga/models { "name": "Documentos de Equipo", "dsl": "type user\n\ntype team\n relations\n define member: [user]\n define admin: [user]\n\ntype document\n relations\n define owner: [user]\n define team: [team]\n define editor: [user, team#admin]\n define viewer: [user, team#member] or editor or owner\n" }

Paso 2: Activar el modelo

POST /api/fga/models/{modelId}/activate

Paso 3: Escribir tuplas

POST /api/fga/tuples/bulk { "writes": [ { "objectType": "team", "objectId": "engineering", "relation": "member", "subjectType": "user", "subjectId": "alice" }, { "objectType": "team", "objectId": "engineering", "relation": "member", "subjectType": "user", "subjectId": "bob" }, { "objectType": "team", "objectId": "engineering", "relation": "admin", "subjectType": "user", "subjectId": "alice" }, { "objectType": "document", "objectId": "arch-doc", "relation": "team", "subjectType": "team", "subjectId": "engineering" }, { "objectType": "document", "objectId": "arch-doc", "relation": "editor", "subjectType": "team", "subjectId": "engineering", "subjectRelation": "admin" }, { "objectType": "document", "objectId": "arch-doc", "relation": "viewer", "subjectType": "team", "subjectId": "engineering", "subjectRelation": "member" } ] }

Paso 4: Verificar acceso

POST /api/fga/check { "objectType": "document", "objectId": "arch-doc", "relation": "editor", "subjectType": "user", "subjectId": "alice", "explain": true } // Resultado: { "allowed": true } — Alice es admin de engineering, lo que le otorga editor
POST /api/fga/check { "objectType": "document", "objectId": "arch-doc", "relation": "editor", "subjectType": "user", "subjectId": "bob" } // Resultado: { "allowed": false } — Bob es miembro pero no admin

Paso 5: Listar objetos que un usuario puede ver

POST /api/fga/list-objects { "objectType": "document", "relation": "viewer", "subjectType": "user", "subjectId": "bob" } // Resultado: { "objectIds": ["arch-doc"] } — Bob puede ver mediante la membresía del equipo

Relacionado