OpenFGA: guida all'autorizzazione ReBAC quando RBAC non basta

Immagine di copertina

Se hai mai ricevuto una richiesta tipo “Alice deve poter modificare il corso 42, ma non il corso 50”, sai già dove finisce la storia. Con dei normali permessi basati sul ruolo dell’utente queste casistiche non possono essere rappresentate: Alice ha il ruolo PowerUser che le consente di operare con quei permessi su tutto l’applicativo, non abbiamo la possibilità di indicare un accesso ad una risorsa specifica.

E allora cosa fai? Aggiungi un ruolo PowerUserLimited. La settimana dopo arriva un caso simile ma leggermente diverso, e ne crei un altro. Dopo sei mesi hai una decina di ruoli con nomi sempre più creativi, nessuno sa con certezza cosa implica ciascuno, e ogni nuovo requisito diventa un meeting per decidere se serve un ruolo nuovo o se quello esistente “basta così”. Frustrante, vero?

La richiesta di Alice è ragionevole. Il problema è il modello di autorizzazione adottato. Se hai già risolto l’autenticazione con Keycloak (chi è l’utente, come gestire login, ruoli e sessioni), il passo successivo è rispondere a una domanda diversa: cosa quell’utente può fare su una risorsa specifica. Qui entra in gioco OpenFGA: un’implementazione open source di Zanzibar, il sistema di autorizzazione che Google usa per Drive e YouTube. Il modello si chiama ReBAC (Relationship-Based Access Control): invece di assegnare ruoli globali, modella relazioni tra utenti e risorse specifiche e risponde a domande del tipo “Alice ha editor su questo corso?”.


RBAC (Role-Based Access Control): dov’è il problema

Con RBAC assegni ruoli globali all’utente (admin, editor, viewer) e verifichi staticamente quali ruoli un utente ricopre prima di permettere un’operazione. Il tuo codice non sa nulla della risorsa specifica: sa solo se l’utente porta il tag giusto.

Per sistemi semplici, con pochi ruoli stabili applicati in modo uniforme a tutto il dominio, è la scelta giusta. Hai un CRUD interno con tre livelli che si comportano allo stesso modo su ogni entità? RBAC risolve il problema in modo pulito e comprensibile. Non serve altro.

Il modello inizia a cedere quando i permessi diventano granulari per risorsa: “Alice può editare questo documento, non quello” è una frase perfettamente sensata che RBAC non sa rappresentare. L’unica via d’uscita che ti resta è moltiplicare i ruoli (uno per ogni combinazione utente-risorsa), oppure spostare la logica nei controller, rischiando di produrre codice difficile da mantenere e comprendere.

Poi ci sono le gerarchie. Se hai accesso a un corso, dovresti avere automaticamente accesso alle sue lezioni? Con RBAC devi propagare quella decisione manualmente in ogni endpoint che tocca le lezioni, tenendola sincronizzata con l’endpoint del corso. Ogni cambiamento di policy ti costringe a ritrovare tutti i posti in cui quella logica è stata duplicata.

L’ultimo punto è la delega parziale: “Mario gestisce gli utenti del team A, non di tutto il sistema” è un requisito comune nelle applicazioni multi-tenant. Con RBAC Mario ha user_manager globale, o non lo ha. La sfumatura “solo per il team A” non esiste nel modello, e finisce per vivere in una colonna del database che il sistema di autorizzazione non conosce.


Come funziona OpenFGA

OpenFGA rappresenta i permessi come un grafo: scrivi relazioni specifiche tra soggetti e oggetti, poi regole per derivare i permessi da quelle relazioni. Il punto di ingresso è minimalista: una struttura dati e una chiamata HTTP.

Passo 1: la tupla

L’unica struttura dati di OpenFGA è la tupla (soggetto, relazione, oggetto): ogni permesso corrisponde a una tripla memorizzata nello store. Niente tabelle con colonne fisse, niente schemi da migrare, ma solamente un grafo che cresce con i tuoi dati.

user:alice  editor  course:42

Leggila così: “Alice è editor del corso 42”. È tutto quello che serve per dire che Alice ha un permesso su quella risorsa specifica. Scrivi una tupla per ogni permesso assegnato e il grafo si aggiorna di conseguenza.

💡 L’OpenFGA Playground è un ambiente online dove puoi scrivere il DSL, aggiungere tuple e lanciare check interattivamente senza installare nulla. Utile per esplorare il modello prima di integrarlo.

Passo 2: il DSL

Le tuple descrivono lo stato corrente, ma da sole non bastano: ti servono regole che spieghino cosa quelle relazioni significano nel tuo dominio. Il DSL di OpenFGA definisce i tipi di risorsa, le relazioni dirette e i permessi computati derivati da quelle relazioni.

type course
  relations
    define editor: [user]
    define root:   [user]
    define can_edit: editor or root

Qui editor e root sono relazioni dirette: le assegni scrivendo una tupla. can_edit invece è un permesso computato: deriva dal DSL e non lo assegni mai direttamente. Quando chiedi “Alice può fare can_edit su course:42?”, OpenFGA valuta la regola editor or root e cerca nel grafo se Alice è editor o root del corso.

⚠️ La distinzione tra relazione diretta e permesso computato è il concetto più importante dell’intero modello. Se usi can_edit (permesso computato) dove il DSL si aspetta una relazione diretta, OpenFGA rifiuta la Write API con un errore di validazione: le relazioni computate non hanno type restriction, quindi non sono assegnabili direttamente. Tienilo a mente prima di scrivere la prima tupla.

La Check API è il modo in cui poni quella domanda:

POST /stores/{store-id}/check
{
  "tuple_key": {
    "user": "user:alice",
    "relation": "can_edit",
    "object": "course:42"
  }
}

La risposta è { "allowed": true }. Il codice applicativo non contiene logica di autorizzazione, ma solo una chiamata HTTP.

Passo 3: la gerarchia

Il modello diventa espressivo quando aggiungi ereditarietà tra tipi diversi. Definisci il tipo lesson con una relazione parent verso il corso, un editor locale e un permesso computato che combina i due:

type lesson
  relations
    define parent: [course]
    define editor: [user]
    define can_edit: editor or editor from parent

La sintassi editor from parent si legge: “hai editor su questa lezione se sei editor sul parent”. Qui parent è dichiarata come [course], quindi è una relazione diretta. Con queste due tuple:

user:alice  editor  course:42
course:42   parent  lesson:l1

Alice ottiene automaticamente can_edit su lesson:l1 senza nessuna tupla diretta sulla lezione. Quando il grafo cresce, l’ereditarietà scala da sola: non tocchi il codice dell’applicazione per propagare la policy.

ListObjects: la query inversa

Oltre a Check, OpenFGA espone la query inversa: “dammi tutti gli oggetti su cui Alice ha can_edit”. Ti serve eccome quando devi costruire UI che mostrano solo le risorse accessibili, senza chiamare Check per ogni elemento della lista.

POST /stores/{store-id}/list-objects
{
  "user": "user:alice",
  "relation": "can_edit",
  "type": "course"
}

La risposta è { "objects": ["course:42", "course:99"] }. Questa è la query che alimenta il filtro delle liste nel frontend: costruisci viste personalizzate per ogni utente senza caricare tutto in memoria e filtrare lato applicazione.

In pratica, sposti tutta la logica di autorizzazione fuori dal tuo backend. Il codice applicativo fa una domanda, OpenFGA risponde.


Quando scegliere OpenFGA

RBAC non è sbagliato: è la scelta giusta per molti sistemi. Se hai pochi ruoli stabili che si applicano uniformemente a tutto il dominio, non serve altro. Il problema è quando il modello inizia a cedere sotto requisiti ragionevoli.

Tre segnali concreti:

  • I ruoli proliferano — l’enum dei ruoli cresce a ogni sprint. Ogni nuovo requisito “quasi uguale al precedente” genera un ruolo nuovo invece di una regola nuova.
  • La logica di autorizzazione finisce nel codice applicativo — i tuoi controller contengono if che dipendono dalla risorsa specifica, non solo dal ruolo dell’utente. Quella logica è duplicata in più endpoint, ognuno con la propria versione della verità.
  • Le gerarchie si propagano a mano — dare accesso a un corso significa ricordarsi di aggiornare separatamente gli endpoint delle lezioni. Ogni cambiamento di policy richiede di ritrovare tutti i posti in cui quella logica è stata copiata.

Se riconosci uno di questi pattern, OpenFGA ti dà gli strumenti per uscirne. Se non li riconosci, aggiungerlo sarebbe complessità inutile: RBAC è più semplice da ragionare e da mantenere quando il dominio lo permette.


Errori da evitare

Assegnare un permesso computato nelle tuple — scrivi user:alice editor course:42, non user:alice can_edit course:42. can_edit è derivato dal DSL: non puoi assegnarlo direttamente. Se lo fai, OpenFGA rifiuta la scrittura con un errore di validazione HTTP 400 (le relazioni computate non hanno type restriction). L’errore arriva subito, alla Write: è preferibile a scoprirlo mesi dopo da un Check che ritorna false senza spiegazioni.

Ricreare i ruoli RBAC come relazioni globali — definire define admin: [user] e usarla su tutto il dominio è portarsi il problema originale dentro OpenFGA. Il modello ha senso quando la relazione è tra utente e risorsa specifica. Se la relazione non dipende dalla risorsa, stai solo aggiungendo complessità senza guadagnare nulla rispetto a RBAC.

Usare Check in loop per filtrare liste — chiamare Check per ogni elemento di una lista per decidere cosa mostrare è lento e non scala. ListObjects esiste per questo: una chiamata sola ti restituisce tutti gli oggetti accessibili. Usalo ogni volta che devi costruire una vista personalizzata per l’utente.


Conclusione

OpenFGA affianca il tuo identity provider, non lo sostituisce. Keycloak (o qualsiasi altro IdP) ti dice chi è l’utente; OpenFGA ti dice cosa può fare su quella risorsa specifica. I due layer risolvono problemi diversi e vivono accanto senza sovrapporsi.

Il punto di ingresso è semplice: una tupla, una Check API. Il modello scala a scenari complessi senza moltiplicare i ruoli nel tuo IdP. Il costo di adozione è reale: un servizio in più, uno store da gestire. Il guadagno è avere la logica di autorizzazione in un posto solo, interrogabile e modificabile senza toccare il codice applicativo.

Se ti è sembrato ragionevole fin qui, il passo successivo è metterlo in pratica. Nel prossimo articolo vediamo come avviare OpenFGA in Docker, scrivere il modello DSL per un dominio reale e collegarlo a un’applicazione esistente — partendo da zero, senza assumere nulla di già configurato.


Risorse utili

Leggi anche

Conosci meglio chi ha scritto questo articolo

Francesco Montelli

Ciao, sono Francesco. Sono un freelance software engineer con oltre 5 anni di esperienza, e mi occupo prevalentemente di sviluppo backend e DevOps aiutando i miei clienti a costruire infrastrutture solide, automatizzate e scalabili. Mi piace andare a fondo nelle cose e condividere quello che imparo. Per questo curo un blog e creo contenuti tecnici che esplorano cosa succede “sotto il cofano”, in modo chiaro e accessibile. Quando non lavoro per i clienti, sperimento nel mio homelab — un ambiente dove metto alla prova idee, strumenti e workflow prima di portarli in produzione.

Foto di Francesco Montelli

Partners

Community, aziende e persone che supportano attivamente il blog

Vuoi diventare partner?

Collaboriamo con community, aziende e strumenti del mondo tech che vogliono raggiungere la nostra community di dev ed engineer italiani. Scrivici per proporre una collaborazione.

✉️ Scrivici

Vuoi diventare tech content creator? 🖊️

Se ti va di raccontare la tua esperienza nel mondo tech, questo è il posto giusto.

Cerchiamo voci autentiche, esempi pratici e punti di vista utili per chi legge.

Scrivici a collaborazioni[at]theredcode.it con una proposta: idea, taglio del contenuto e una breve presentazione. Non vediamo l'ora di leggere la tua esperienza!

Invia la tua idea