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:
- Los Modelos de Autorización definen tipos de objetos, sus relaciones y reglas de reescritura usando un DSL compatible con OpenFGA.
- Las Tuplas de Relaciones son los hechos del sistema. Cada tupla afirma que un sujeto tiene una relación con un objeto.
- 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 ownerTipos de reglas de reescritura:
| Regla | Sintaxis | Descripción |
|---|---|---|
Directa (this) | [user] | El sujeto debe ser asignado directamente mediante una tupla |
| Unión | A or B | El sujeto debe satisfacer al menos una de las relaciones |
| Intersección | A and B | El sujeto debe satisfacer todas las relaciones |
| Exclusión | A but not B | El sujeto debe satisfacer A y no debe satisfacer B |
| Conjunto de usuarios calculado | owner | Hereda de otra relación en el mismo objeto |
| Tupla a conjunto de usuarios | group#member | Sigue 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
/api/fga/modelsRequires: view:fga_modelsLista 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ámetro | Tipo | Descripción |
|---|---|---|
page | integer | Número de página (por defecto: 1) |
limit | integer | Elementos 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
/api/fga/modelsRequires: manage:fga_modelsCrea 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ódigo | HTTP | Descripción |
|---|---|---|
DSL_PARSE_ERROR | 400 | La sintaxis del DSL es inválida. El campo message incluye el número de línea y la descripción del error |
DSL_VALIDATION_ERROR | 400 | El DSL es sintácticamente válido pero referencia tipos, relaciones no definidos o contiene ciclos |
VALIDATION_ERROR | 400 | Faltan 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
/api/fga/models/[id]Requires: view:fga_modelsRecupera 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ódigo | HTTP | Descripción |
|---|---|---|
NOT_FOUND | 404 | El modelo no existe o pertenece a un tenant diferente |
Actualizar Modelo
/api/fga/models/[id]Requires: manage:fga_modelsActualiza 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ódigo | HTTP | Descripción |
|---|---|---|
NOT_FOUND | 404 | El modelo no existe |
DSL_PARSE_ERROR | 400 | El DSL actualizado tiene errores de sintaxis |
DSL_VALIDATION_ERROR | 400 | El DSL actualizado referencia tipos o relaciones no definidos |
Eliminar Modelo
/api/fga/models/[id]Requires: manage:fga_modelsElimina 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ódigo | HTTP | Descripción |
|---|---|---|
NOT_FOUND | 404 | El modelo no existe |
MODEL_IS_ACTIVE | 400 | No se puede eliminar el modelo activo actualmente |
Activar Modelo
/api/fga/models/[id]/activateRequires: manage:fga_modelsEstablece 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ódigo | HTTP | Descripción |
|---|---|---|
NOT_FOUND | 404 | El modelo no existe |
ALREADY_ACTIVE | 400 | Este 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
/api/fga/tuplesRequires: view:fga_tuplesLista 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ámetro | Tipo | Descripción |
|---|---|---|
objectType | string | Filtrar por tipo de objeto (p. ej., document) |
objectId | string | Filtrar por ID de objeto |
relation | string | Filtrar por nombre de relación |
subjectType | string | Filtrar por tipo de sujeto |
subjectId | string | Filtrar por ID de sujeto |
page | integer | Número de página (por defecto: 1) |
limit | integer | Elementos 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
/api/fga/tuplesRequires: manage:fga_tuplesEscribe 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ódigo | HTTP | Descripción |
|---|---|---|
NO_ACTIVE_MODEL | 400 | No existe modelo de autorización activo para este tenant |
INVALID_TYPE | 400 | El tipo de objeto no existe en el modelo activo |
INVALID_RELATION | 400 | La relación no existe en este tipo de objeto en el modelo activo |
INVALID_SUBJECT_TYPE | 400 | La relación no acepta este tipo de sujeto como destino válido |
TUPLE_EXISTS | 409 | Ya existe una tupla idéntica |
VALIDATION_ERROR | 400 | Faltan campos requeridos |
Eliminar Tupla
/api/fga/tuplesRequires: manage:fga_tuplesElimina 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
/api/fga/tuples/bulkRequires: manage:fga_tuplesEscribe 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ódigo | HTTP | Descripción |
|---|---|---|
NO_ACTIVE_MODEL | 400 | No existe modelo de autorización activo |
INVALID_RELATION | 400 | Una tupla referencia una relación que no existe en el modelo activo |
INVALID_TYPE | 400 | Una tupla referencia un tipo de objeto que no está en el modelo activo |
BATCH_TOO_LARGE | 400 | El total de escrituras + eliminaciones supera el tamaño máximo del lote |
VALIDATION_ERROR | 400 | Una 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
/api/fga/checkRequires: debug:fgaVerifica 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
}| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
objectType | string | Sí | El tipo del objeto objetivo |
objectId | string | Sí | El ID del objeto objetivo |
relation | string | Sí | La relación a verificar |
subjectType | string | Sí | El tipo del sujeto (normalmente user) |
subjectId | string | Sí | El ID del sujeto |
explain | boolean | No | Cuando 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ódigo | HTTP | Descripción |
|---|---|---|
NO_ACTIVE_MODEL | 400 | No existe modelo de autorización activo |
INVALID_TYPE | 400 | El tipo de objeto no existe en el modelo activo |
INVALID_RELATION | 400 | La relación no existe en el tipo especificado |
MAX_DEPTH_EXCEEDED | 400 | La evaluación superó la profundidad máxima de recursión de 25 |
VALIDATION_ERROR | 400 | Faltan 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
/api/fga/expandRequires: debug:fgaExpande 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"
}| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
objectType | string | Sí | El tipo del objeto objetivo |
objectId | string | Sí | El ID del objeto objetivo |
relation | string | Sí | 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ódigo | HTTP | Descripción |
|---|---|---|
NO_ACTIVE_MODEL | 400 | No existe modelo de autorización activo |
INVALID_TYPE | 400 | El tipo de objeto no fue encontrado en el modelo |
INVALID_RELATION | 400 | La relación no fue encontrada en este tipo |
List Objects
/api/fga/list-objectsRequires: debug:fgaLista 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"
}| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
objectType | string | Sí | El tipo de objetos a buscar |
relation | string | Sí | La relación que el sujeto debe tener |
subjectType | string | Sí | El tipo del sujeto |
subjectId | string | Sí | El ID del sujeto |
page | integer | No | Número de página para paginación (por defecto: 1) |
limit | integer | No | Elementos 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ódigo | HTTP | Descripción |
|---|---|---|
NO_ACTIVE_MODEL | 400 | No existe modelo de autorización activo |
INVALID_TYPE | 400 | El tipo de objeto no fue encontrado en el modelo |
INVALID_RELATION | 400 | La 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
| Permiso | Descripción |
|---|---|
view:fga_models | Ver modelos de autorización y sus definiciones DSL |
manage:fga_models | Crear, actualizar, eliminar y activar modelos de autorización |
view:fga_tuples | Listar y leer tuplas de relaciones |
manage:fga_tuples | Escribir y eliminar tuplas de relaciones (individuales y masivas) |
debug:fga | Ejecutar 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}/activatePaso 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 editorPOST /api/fga/check
{
"objectType": "document",
"objectId": "arch-doc",
"relation": "editor",
"subjectType": "user",
"subjectId": "bob"
}
// Resultado: { "allowed": false } — Bob es miembro pero no adminPaso 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 equipoRelacionado
- Modelo FGA / Zanzibar — Cómo funcionan el modelo de autorización y las reglas de reescritura
- Guía de Autorización Detallada — Guía práctica para implementar FGA
- Depurador FGA — Prueba verificaciones e inspecciona árboles de resolución en la Consola
- API de Roles y Permisos — Endpoints RBAC que complementan FGA
- SDK de JavaScript —
client.fga.check()y gestión de tuplas desde el SDK