Skip to Content

Feingranulare Autorisierungs-API (FGA)

Die Feingranulare-Autorisierungs-Engine bietet Zanzibar-style beziehungsbasierte Zugriffssteuerung (ReBAC). Sie erweitert die RBAC-Schicht um objektbasierte Autorisierung: Anstatt “Benutzer hat Berechtigung X global” beantwortet FGA: “Hat Benutzer Alice die Relation viewer auf Dokument readme?”

Das System basiert auf drei Konzepten:

  1. Autorisierungsmodelle definieren Objekttypen, ihre Relationen und Umschreibungsregeln mithilfe eines OpenFGA-kompatiblen DSL.
  2. Beziehungs-Tupel sind die Fakten des Systems. Jedes Tupel stellt fest, dass ein Subjekt eine Beziehung zu einem Objekt hat.
  3. Autorisierungsabfragen (check, expand, list-objects) werten Tupel anhand der Umschreibungsregeln des Modells aus.

Alle FGA-Endpunkte erfordern den x-tenant-Header und ein gültiges Zugriffs-Token.

Autorisierungsmodell-DSL

Das DSL definiert Typen und ihre Relationen. Jede Relation kann Umschreibungsregeln haben, die den Zugang aus anderen Relationen oder indirekten Beziehungen zusammensetzen.

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

Umschreibungsregel-Typen:

RegelSyntaxBeschreibung
Direkt (this)[user]Subjekt muss direkt über ein Tupel zugewiesen sein
UnionA or BSubjekt muss mindestens eine der Relationen erfüllen
IntersectionA and BSubjekt muss alle Relationen erfüllen
ExclusionA but not BSubjekt muss A erfüllen und darf B nicht erfüllen
Computed UsersetownerErbt von einer anderen Relation auf demselben Objekt
Tuple-to-Usersetgroup#memberFolgt einer Relation auf einem verwandten Objekt (indirekt)

Die FGA-Engine verwendet rekursive Auswertung mit einer maximalen Tiefe von 25 und Zykluserkennung über ein besuchtes Set. Dies verhindert Endlosschleifen in zirkulären Relationsdefinitionen.

Autorisierungsmodelle

Modelle auflisten

GET/api/fga/modelsRequires: view:fga_models

Alle Autorisierungsmodelle für den Tenant auflisten, nach Version absteigend geordnet. Es kann nur ein Modell gleichzeitig aktiv sein. Das aktive Modell wird für alle Tupelvalidierungen und Autorisierungsabfragen verwendet.

Abfrageparameter

ParameterTypBeschreibung
pageintegerSeitennummer (Standard: 1)
limitintegerElemente pro Seite (Standard: 20)

Erfolgsantwort

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

Modell erstellen

POST/api/fga/modelsRequires: manage:fga_models

Ein neues Autorisierungsmodell erstellen, indem ein Name und eine DSL-Definition bereitgestellt werden. Das DSL wird zeilenweise geparst und vor der Speicherung validiert. Wenn das DSL Syntaxfehler enthält oder undefinierte Typen oder Relationen referenziert, wird die Anforderung mit einer detaillierten Fehlermeldung einschließlich der Zeilennummer abgelehnt.

Anforderungs-Body

{ "name": "Dokumentzugriffsmodell", "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" }

Erfolgsantwort

{ "ok": true, "data": { "id": "model_ghi789", "name": "Dokumentzugriffsmodell", "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" } }

Fehlercodes

CodeHTTPBeschreibung
DSL_PARSE_ERROR400DSL-Syntax ist ungültig. Das message-Feld enthält die Zeilennummer und Beschreibung des Fehlers
DSL_VALIDATION_ERROR400DSL ist syntaktisch korrekt, referenziert aber undefinierte Typen, Relationen oder enthält Zyklen
VALIDATION_ERROR400Fehlende Pflichtfelder (name oder dsl)

DSL-Parse-Fehler-Beispiel

{ "ok": false, "error": { "code": "DSL_PARSE_ERROR", "message": "Zeile 7: Unbekanntes Schlüsselwort 'defines'. Meintest du 'define'?" } }

Modell abrufen

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

Ein bestimmtes Autorisierungsmodell anhand seiner ID abrufen. Gibt das vollständige Modell zurück, einschließlich des rohen DSL-Texts und des geparsten Schema-Objekts.

Erfolgsantwort

{ "ok": true, "data": { "id": "model_ghi789", "name": "Dokumentzugriffsmodell", "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" } }

Fehlercodes

CodeHTTPBeschreibung
NOT_FOUND404Modell existiert nicht oder gehört einem anderen Tenant

Modell aktualisieren

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

Den Namen oder die DSL-Definition eines Modells aktualisieren. Wenn das DSL aktualisiert wird, wird es erneut geparst und validiert. Die Version des Modells wird bei einer Aktualisierung nicht automatisch erhöht — erstelle ein neues Modell für versionierte Änderungen.

Anforderungs-Body

{ "name": "Dokumentzugriffsmodell v2", "dsl": "type user\n\ntype document\n relations\n define owner: [user]\n define viewer: [user] or owner\n" }

name und dsl sind optional. Nur bereitgestellte Felder werden aktualisiert.

Erfolgsantwort

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

Fehlercodes

CodeHTTPBeschreibung
NOT_FOUND404Modell existiert nicht
DSL_PARSE_ERROR400Aktualisiertes DSL hat Syntaxfehler
DSL_VALIDATION_ERROR400Aktualisiertes DSL referenziert undefinierte Typen oder Relationen

Modell löschen

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

Ein Autorisierungsmodell löschen. Aktive Modelle können nicht gelöscht werden — du musst zunächst ein anderes Modell aktivieren.

Das Löschen eines Modells löscht nicht automatisch Tupel, die dagegen geschrieben wurden. Verwaiste Tupel werden von Autorisierungsabfragen ignoriert, verbleiben aber in der Datenbank, bis sie manuell bereinigt werden.

Erfolgsantwort

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

Fehlercodes

CodeHTTPBeschreibung
NOT_FOUND404Modell existiert nicht
MODEL_IS_ACTIVE400Das aktuell aktive Modell kann nicht gelöscht werden

Modell aktivieren

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

Ein Modell als das aktive Autorisierungsmodell für den Tenant setzen. Jedes zuvor aktive Modell wird automatisch deaktiviert. Alle nachfolgenden check-, expand- und list-objects-Abfragen verwenden die Umschreibungsregeln des neu aktivierten Modells. Der Modell-Cache wird sofort invalidiert.

Anforderung: Kein Body erforderlich.

Erfolgsantwort

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

Fehlercodes

CodeHTTPBeschreibung
NOT_FOUND404Modell existiert nicht
ALREADY_ACTIVE400Dieses Modell ist bereits das aktive Modell

Die FGA-Engine cached das aktive Modell im Speicher mit einer TTL von 5 Minuten. Nach der Aktivierung eines neuen Modells können Abfragen auf anderen Server-Instanzen bis zu 5 Minuten lang das alte Modell verwenden. Force-Refresh erfolgt sofort auf der Instanz, die die Aktivierungsanforderung verarbeitet hat.

Beziehungs-Tupel

Tupel sind die Autorisierungsfakten. Jedes Tupel gibt an, dass ein Subjekt eine Beziehung zu einem Objekt hat.

Tupel-Format: objektTyp:objektId#relation@subjektTyp:subjektId

Beispiele:

  • document:readme#viewer@user:alice — Alice ist ein Viewer des Dokuments “readme”
  • document:readme#editor@group:engineering#member — Mitglieder der Gruppe “engineering” sind Editoren des Dokuments “readme”
  • folder:projects#owner@user:bob — Bob besitzt den Ordner “projects”

Tupel auflisten

GET/api/fga/tuplesRequires: view:fga_tuples

Beziehungs-Tupel mit optionaler Filterung auflisten. Mindestens ein Filterparameter wird empfohlen, um zu vermeiden, den gesamten Tupel-Speicher zurückzugeben. Unterstützt Paginierung.

Abfrageparameter

ParameterTypBeschreibung
objectTypestringNach Objekttyp filtern (z.B. document)
objectIdstringNach Objekt-ID filtern
relationstringNach Relationsname filtern
subjectTypestringNach Subjekttyp filtern
subjectIdstringNach Subjekt-ID filtern
pageintegerSeitennummer (Standard: 1)
limitintegerElemente pro Seite (Standard: 20, max: 100)

Erfolgsantwort

{ "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 } } }

Tupel schreiben

POST/api/fga/tuplesRequires: manage:fga_tuples

Ein einzelnes Beziehungs-Tupel schreiben. Das Tupel wird vor der Speicherung gegen das aktive Autorisierungsmodell validiert. Der Objekttyp, die Relation und der Subjekttyp müssen im aktiven Modell existieren, und die Relation muss den Subjekttyp als gültiges Zuweisungsziel akzeptieren.

Anforderungs-Body — direkte Benutzerzuweisung

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

Anforderungs-Body — Subjektmenge (indirekt/Gruppenmitgliedschaft)

Wenn das Subjekt kein einzelner Benutzer ist, sondern eine Menge von Benutzern, die durch eine Relation auf einem anderen Objekt definiert ist, schließe subjectRelation ein:

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

Das bedeutet: “Alle Entitäten, die die member-Relation auf group:engineering haben, sind auch editor auf document:readme.”

Erfolgsantwort

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

Fehlercodes

CodeHTTPBeschreibung
NO_ACTIVE_MODEL400Kein aktives Autorisierungsmodell für diesen Tenant
INVALID_TYPE400Objekttyp existiert nicht im aktiven Modell
INVALID_RELATION400Relation existiert nicht auf diesem Objekttyp im aktiven Modell
INVALID_SUBJECT_TYPE400Die Relation akzeptiert diesen Subjekttyp nicht als gültiges Ziel
TUPLE_EXISTS409Ein identisches Tupel existiert bereits
VALIDATION_ERROR400Fehlende Pflichtfelder

Tupel löschen

DELETE/api/fga/tuplesRequires: manage:fga_tuples

Ein bestimmtes Beziehungs-Tupel löschen, indem die Tupeldaten im Anforderungs-Body angegeben werden. Alle Tupelfelder müssen exakt übereinstimmen. Gibt Erfolg zurück, auch wenn das Tupel nicht existiert (idempotentes Löschen).

Anforderungs-Body

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

Erfolgsantwort

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

Tupel massenweise schreiben/löschen

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

Mehrere Tupel in einer einzigen atomaren Anforderung schreiben und/oder löschen. Alle Operationen im Batch werden gegen das aktive Modell validiert, bevor Schreibvorgänge erfolgen. Wenn ein einzelnes Tupel die Validierung nicht besteht, wird der gesamte Batch abgelehnt und keine Änderungen vorgenommen.

Anforderungs-Body

{ "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" } ] }

writes und deletes sind optional, aber mindestens eines muss vorhanden sein. Jedes Array kann bis zu 100 Tupel enthalten.

Erfolgsantwort

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

Fehlercodes

CodeHTTPBeschreibung
NO_ACTIVE_MODEL400Kein aktives Autorisierungsmodell
INVALID_RELATION400Ein Tupel referenziert eine Relation, die nicht im aktiven Modell existiert
INVALID_TYPE400Ein Tupel referenziert einen Objekttyp, der nicht im aktiven Modell ist
BATCH_TOO_LARGE400Gesamt-Writes + Deletes überschreitet die maximale Batch-Größe
VALIDATION_ERROR400Ein oder mehrere Tupel haben fehlende Pflichtfelder

Bulk-Operationen sind atomar. Wenn Tupel #47 von 100 die Validierung nicht besteht, wird keines der 100 Tupel geschrieben oder gelöscht. Prüfe die Fehlerantwort für das spezifisch fehlgeschlagene Tupel.

Autorisierungsabfragen

Check

POST/api/fga/checkRequires: debug:fga

Prüfen, ob ein Subjekt eine bestimmte Relation zu einem Objekt hat. Die Engine wertet die vollständigen Umschreibungsregeln des aktiven Modells rekursiv aus und folgt dabei Computed Usersets, Tuple-to-Userset-Indirektionen, Unions, Intersections und Exclusions. Gibt optional einen Auflösungsbaum zurück, der zeigt, wie die Entscheidung getroffen wurde.

Anforderungs-Body

{ "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice", "explain": false }
FeldTypErforderlichBeschreibung
objectTypestringJaDer Typ des Zielobjekts
objectIdstringJaDie ID des Zielobjekts
relationstringJaDie zu prüfende Relation
subjectTypestringJaDer Typ des Subjekts (typischerweise user)
subjectIdstringJaDie ID des Subjekts
explainbooleanNeinWenn true, wird der Auflösungsbaum eingeschlossen (Standard: false)

Erfolgsantwort (ohne Erklärung)

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

Erfolgsantwort (mit Erklärung)

{ "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" } ] } ] } } }

Der Auflösungsbaum zeigt: Alice ist kein direkter viewer und kein editor, aber sie ist ein owner, und die viewer-Relation enthält or owner, daher besteht die Prüfung.

Fehlercodes

CodeHTTPBeschreibung
NO_ACTIVE_MODEL400Kein aktives Autorisierungsmodell
INVALID_TYPE400Objekttyp existiert nicht im aktiven Modell
INVALID_RELATION400Relation existiert nicht auf dem angegebenen Typ
MAX_DEPTH_EXCEEDED400Auswertung hat die maximale Rekursionstiefe von 25 überschritten
VALIDATION_ERROR400Fehlende Pflichtfelder

In der Produktion sollten Ressourcenserver den Check-Endpunkt mit einem M2M-Token mit debug:fga aufrufen, anstatt ihn Endbenutzern zugänglich zu machen. Der explain-Modus ist besonders nützlich während der Entwicklung und beim Debuggen, verursacht aber Overhead und sollte in kritischen Pfaden deaktiviert werden.

Expand

POST/api/fga/expandRequires: debug:fga

Eine Relation auf einem Objekt erweitern, um alle Subjekte zu entdecken, die diese Relation haben. Gibt eine Baumstruktur zurück, die den Umschreibungsregeln folgt und sowohl direkte Tupelzuweisungen als auch indirekte Beziehungen zeigt (Computed Usersets, Tuple-to-Userset).

Anforderungs-Body

{ "objectType": "document", "objectId": "readme", "relation": "viewer" }
FeldTypErforderlichBeschreibung
objectTypestringJaDer Typ des Zielobjekts
objectIdstringJaDie ID des Zielobjekts
relationstringJaDie zu erweiternde Relation

Erfolgsantwort

{ "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" } ] } ] } } } }

Dies zeigt, dass document:readme folgende Viewer hat: Dave und Eve (direkt), Bob (über editor) und Alice (über owner).

Fehlercodes

CodeHTTPBeschreibung
NO_ACTIVE_MODEL400Kein aktives Autorisierungsmodell
INVALID_TYPE400Objekttyp nicht im Modell gefunden
INVALID_RELATION400Relation nicht auf diesem Typ gefunden

Objekte auflisten

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

Alle Objekte eines bestimmten Typs auflisten, auf die ein Subjekt über eine bestimmte Relation zugreifen kann. Führt einen Reverse-Lookup durch den Tupel-Speicher und die Umschreibungsregeln durch. Nützlich für den Aufbau gefilterter Ansichten wie “zeige mir alle Dokumente, die dieser Benutzer ansehen kann”.

Anforderungs-Body

{ "objectType": "document", "relation": "viewer", "subjectType": "user", "subjectId": "alice" }
FeldTypErforderlichBeschreibung
objectTypestringJaDer Typ der zu suchenden Objekte
relationstringJaDie Relation, die das Subjekt haben muss
subjectTypestringJaDer Typ des Subjekts
subjectIdstringJaDie ID des Subjekts
pageintegerNeinSeitennummer für Paginierung (Standard: 1)
limitintegerNeinElemente pro Seite (Standard: 100, max: 1000)

Erfolgsantwort

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

Fehlercodes

CodeHTTPBeschreibung
NO_ACTIVE_MODEL400Kein aktives Autorisierungsmodell
INVALID_TYPE400Objekttyp nicht im Modell gefunden
INVALID_RELATION400Relation nicht auf diesem Typ gefunden

Die list-objects-Abfrage kann bei großen Tupel-Speichern aufwändig sein, da sie mehrere Tupel durchsuchen und auswerten muss. Verwende Paginierung und erwäge, Ergebnisse für häufig abgerufene Muster zwischenzuspeichern.

Berechtigungsreferenz

BerechtigungBeschreibung
view:fga_modelsAutorisierungsmodelle und ihre DSL-Definitionen anzeigen
manage:fga_modelsAutorisierungsmodelle erstellen, aktualisieren, löschen und aktivieren
view:fga_tuplesBeziehungs-Tupel auflisten und lesen
manage:fga_tuplesBeziehungs-Tupel schreiben und löschen (einzeln und in Bulk)
debug:fgaCheck-, Expand- und List-Objects-Abfragen ausführen

Vollständiges Beispiel

Diese Schritt-für-Schritt-Anleitung demonstriert ein gängiges Autorisierungsmuster: ein teambasiertes Dokumentzugangssystem.

Schritt 1: Modell erstellen

POST /api/fga/models { "name": "Team-Dokumente", "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" }

Schritt 2: Modell aktivieren

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

Schritt 3: Tupel schreiben

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" } ] }

Schritt 4: Zugang prüfen

POST /api/fga/check { "objectType": "document", "objectId": "arch-doc", "relation": "editor", "subjectType": "user", "subjectId": "alice", "explain": true } // Ergebnis: { "allowed": true } — Alice ist ein Admin von engineering, was editor gewährt
POST /api/fga/check { "objectType": "document", "objectId": "arch-doc", "relation": "editor", "subjectType": "user", "subjectId": "bob" } // Ergebnis: { "allowed": false } — Bob ist ein Mitglied, aber kein Admin

Schritt 5: Objekte auflisten, die ein Benutzer ansehen kann

POST /api/fga/list-objects { "objectType": "document", "relation": "viewer", "subjectType": "user", "subjectId": "bob" } // Ergebnis: { "objectIds": ["arch-doc"] } — Bob kann über Teammitgliedschaft ansehen

Zugehörige Referenzen