Menu di navigazione consapevoli delle autorizzazioni che fanno rispettare comunque gli accessi
I menu di navigazione che tengono conto delle autorizzazioni migliorano la chiarezza, ma la sicurezza deve risiedere nel backend. Scopri pattern semplici per ruoli, policy e nascondere l'interfaccia in modo sicuro.

Quale problema risolvono davvero i menu sensibili alle autorizzazioni
Quando le persone dicono “nascondi il pulsante”, di solito intendono una di due cose: ridurre il disordine per utenti che non possono usare una funzione, oppure fermare gli abusi. Solo il primo obiettivo è realistico sul frontend.
I menu sensibili alle autorizzazioni sono principalmente uno strumento UX. Aiutano chi apre l'app a vedere subito cosa può fare, senza imbattersi in schermate “Accesso negato” a ogni seconda clic. Riducendo confusione come “Dove approvo le fatture?” o “Perché questa pagina dà errore?” si abbassa anche il carico sul supporto.
Nascondere l'interfaccia non è sicurezza. È chiarezza.
Anche un collega curioso può comunque:
- Digitare un deep link verso una pagina nascosta se conosce l'URL
- Aprire un vecchio bookmark di una schermata admin
- Chiamare la tua API direttamente con uno script o uno strumento
- Modificare richieste dal browser e provare ID diversi
Quindi il vero problema che i menu risolvono è guidare onestamente l'utente. Mantengono l'interfaccia allineata al lavoro, al ruolo e al contesto dell'utente, rendendo evidente quando qualcosa non è disponibile.
Un buon stato finale somiglia a questo:
- Gli utenti vedono per lo più azioni che hanno senso per loro e raramente si imbattono in errori a sorpresa.
- Gli sviluppatori hanno un'unica fonte di verità per le regole di accesso, invece di controlli sparsi.
- Le modifiche di prodotto sono più sicure perché una nuova voce di menu ti costringe a decidere quale permesso richiede.
- La sicurezza è applicata dove conta: il backend rifiuta le azioni proibite ogni volta, anche se l'interfaccia le mostra per errore.
Esempio: in un piccolo CRM, un Sales Rep dovrebbe vedere Leads e Tasks, ma non User Management. Se incolla comunque l'URL di user management, la pagina dovrebbe fallire chiudendosi e il server dovrebbe bloccare ogni tentativo di elencare utenti o cambiare ruoli.
Visibilità non è autorizzazione
La visibilità è quello che l'interfaccia sceglie di mostrare. L'autorizzazione è quello che il sistema permetterà realmente quando una richiesta arriva al server.
I menu sensibili alle autorizzazioni riducono la confusione. Se qualcuno non sarà mai autorizzato a vedere Billing o Admin, nascondere quegli elementi mantiene l'app pulita e riduce i ticket di supporto. Ma nascondere un pulsante non è una serratura. Le persone possono comunque provare l'endpoint sottostante usando strumenti di sviluppo, un vecchio bookmark o una richiesta copiata.
Una regola pratica: decidete quale esperienza volete offrire, poi applicate la regola nel backend qualunque cosa faccia la UI.
Quando decidete come presentare un'azione, tre pattern coprono la maggior parte dei casi:
- Nascondi quando la funzione dovrebbe essere non scoperta per la maggior parte dei ruoli (per esempio, strumenti solo interni).
- Disattiva quando la funzione esiste ma l'utente non può usarla in questo momento (per esempio, Export disabilitato fino all'upgrade del piano, fino alla selezione di un record, o mentre i dati sono in caricamento).
- Mostra con spiegazione quando gli utenti devono capire perché non possono procedere (“Puoi visualizzare le fatture, ma solo gli Owner possono modificare i metodi di pagamento”).
“Puoi visualizzare ma non modificare” è comune e vale la pena progettarlo esplicitamente. Trattalo come due permessi: uno per leggere i dati e uno per modificarli. Nel menu potresti mostrare i dettagli del Cliente a chiunque possa leggere, ma mostrare Edit customer solo a chi ha accesso in scrittura. Nella pagina, rendi i campi in sola lettura e proteggi i controlli di modifica, permettendo comunque il caricamento della pagina.
La cosa più importante: il backend decide l'esito finale. Anche se la UI nasconde tutte le azioni admin, il server deve comunque verificare i permessi su ogni richiesta sensibile e restituire una risposta chiara “non consentito” quando qualcuno ci prova.
Scegli un modello di permessi che puoi mantenere
Il modo più rapido per rilasciare menu sensibili alle autorizzazioni è partire da un modello che il tuo team riesce a spiegare in una frase. Se non riuscite a spiegarlo, non lo manterrete corretto.
Usate i ruoli per raggruppare, non per dare significato. Admin e Support sono contenitori utili. Ma quando i ruoli cominciano a moltiplicarsi (Admin-West-Coast-ReadOnly), l'interfaccia diventa un labirinto e il backend diventa un gioco di indovinelli.
Preferite i permessi come fonte di verità per ciò che qualcuno può fare. Manteneteli piccoli e basati su azioni, come invoice.create o customer.export. Questo scala meglio della proliferazione di ruoli perché le nuove funzionalità di solito aggiungono nuove azioni, non nuovi titoli di lavoro.
Poi aggiungete policy (regole) per il contesto. Qui gestite “può modificare solo il proprio record” o “può approvare fatture solo sotto $5.000”. Le policy vi evitano di creare decine di permessi quasi duplicati che differiscono solo per una condizione.
Un layering mantenibile assomiglia a questo:
- I ruoli raggruppano i permessi (funzione lavorativa)
- I permessi descrivono azioni (cosa)
- Le policy aggiungono contesto (quando, quali record)
La nomenclatura conta più di quanto si pensi. Se la tua UI dice Export Customers ma l'API usa download_all_clients_v2, finirai per nascondere la cosa sbagliata o bloccare quella giusta. Mantieni i nomi umani, coerenti e condivisi tra frontend e backend:
- Usa
noun.verb(oresource.action) in modo coerente - Allinea le etichette UI all'intento del permesso, non ai nomi interni del codice
- Mantieni i vecchi nomi dei permessi funzionanti quando rinomini funzionalità
Esempio: in un CRM, un ruolo Sales potrebbe includere lead.create e lead.update, ma una policy limita gli aggiornamenti ai lead di proprietà dell'utente. Questo mantiene il menu chiaro mentre il backend resta rigoroso.
Passo dopo passo: implementare ruoli e permessi end-to-end
I menu sensibili alle autorizzazioni fanno sentire bene perché riducono il disordine e prevengono clic accidentali. Ma sono utili solo se il backend resta al comando. Pensate alla UI come un suggerimento e al server come il giudice.
Iniziate scrivendo cosa state proteggendo. Non pagine, ma azioni. Visualizzare la lista clienti è diverso da esportare clienti o cancellare un cliente. Questa è la spina dorsale dei menu che non si trasformano in teatro di sicurezza.
Una ricetta pratica end-to-end
- Inventario delle azioni in UI e API. Per ogni funzionalità, elencate operazioni concrete: read, create, update, delete, export, invite, change billing, run admin reports.
- Definite i permessi sul server come fonte di verità. Memorizzate la mappatura ruolo-permesso nel database (o in config) e fate sì che ogni handler API chieda “questo utente ha il permesso X sulla risorsa Y?”.
- Restituite un piccolo payload di capabilities per la UI. Dopo il login (o al refresh di sessione), restituite boolean come
canEditCustomers,canDeleteCustomers,canExporto una lista compatta di stringhe di permesso. Tenetelo minimale. - Applicate i controlli a ogni write, più letture sensibili. Tutte le mutazioni devono verificare i permessi. Alcune letture dovrebbero farlo anch'esse (per esempio salari, log di audit, export, o qualsiasi endpoint che bypassa i filtri normali).
- Aggiungete qualche test focalizzato sui ruoli. Scegliete 2–3 ruoli chiave e testate le azioni a più alto rischio. Esempio: Sales può creare deal ma non esportare clienti; Admin può invitare utenti.
Una regola importante: non fidatevi mai dei flag di ruolo o permesso forniti dal client. La UI può nascondere pulsanti basandosi sulle capabilities, ma l'API deve comunque rifiutare le richieste non autorizzate.
Pattern frontend che restano onesti
I menu sensibili alle autorizzazioni dovrebbero aiutare le persone a trovare cosa possono fare, non fingere di far rispettare la sicurezza. Il frontend è un corrimano; il backend è la serratura.
Costruisci i menu da una sola fonte di verità
Invece di spargere controlli su ogni pulsante, definisci la navigazione da una singola config che include il permesso richiesto per ogni voce, poi rendi da quella config. Questo mantiene le regole leggibili ed evita controlli dimenticati in angoli strani dell'interfaccia.
Un pattern semplice assomiglia a questo:
const menu = [
{ label: "Contacts", path: "/contacts", requires: "contacts.read" },
{ label: "Export", action: "contacts.export", requires: "contacts.export" },
{ label: "Admin", path: "/admin", requires: "admin.access" },
];
const visibleMenu = menu.filter(item => userPerms.includes(item.requires));
Preferite nascondere intere sezioni (come Admin) invece di spargere controlli su ogni singolo link admin. Sono meno punti dove sbagliare.
Stati onesti: nascosto vs disattivato
Nascondi elementi quando l'utente non sarà mai autorizzato a usarli. Disattiva elementi quando l'utente ha il permesso, ma manca il contesto.
Esempio: Delete contact dovrebbe essere disattivato finché non è selezionato un contatto. Stesso permesso, manca solo il contesto. Quando disattivi, aggiungi un breve “perché” vicino al controllo (tooltip, testo di aiuto o nota inline): Seleziona un contatto per eliminarlo.
Un insieme di regole che regge:
- Nascondi quando l'utente non ha il permesso.
- Disattiva quando l'utente ha il permesso ma non è selezionato alcun elemento, il form è invalido o i dati sono in caricamento.
- Non considerare lo storage locale come verità per i permessi. È solo una cache.
- Rivedi i permessi dopo login, cambio ruolo o cambio account.
Enforcement backend che non si può bypassare
Nascondere le voci aiuta le persone a concentrarsi, ma non protegge nulla. Il backend deve essere il giudice finale perché le richieste possono essere riprodotte, modificate o generate fuori dalla UI.
Una buona regola: ogni azione che cambia dati ha bisogno di un controllo di autorizzazione, in un unico punto, che ogni richiesta attraversa. Può essere middleware, un wrapper per gli handler, o un piccolo layer di policy che chiami all'inizio di ogni endpoint. Scegli un approccio e mantienilo, altrimenti perderai dei percorsi.
Metti il controllo sul percorso della richiesta
Mantieni l'autorizzazione separata dalla validazione dell'input. Prima decidi, “questo utente può farlo?”, poi valida il payload. Se validi prima, puoi rivelare dettagli (come quali ID di record esistono) a qualcuno che non dovrebbe nemmeno sapere che l'azione è possibile.
Un pattern che scala:
- Autentica il chiamante e costruisci un'identità chiara (user id, org id, ruoli, permessi).
- Carica il contesto della risorsa necessario per l'autorizzazione (spesso solo owner id o org id).
- Chiama una singola funzione di policy (per esempio:
Can(user, "invoice.delete", invoice)). - Se negato, fermati subito e restituisci lo status appropriato.
- Solo allora valida ed esegui la logica di business.
Restituisci risposte chiare e coerenti
Usa codici di stato che aiutino sia il frontend sia i log:
401 Unauthorizedquando il chiamante non è autenticato.403 Forbiddenquando è autenticato ma non autorizzato.
Fai attenzione al 404 Not Found come camuffamento. Può essere utile per non rivelare l'esistenza di una risorsa, ma se lo usi in modo incoerente il debugging diventa difficile. Scegli una regola coerente per tipo di risorsa.
Assicurati che la stessa autorizzazione giri sia che l'azione provenga da un click UI, un'app mobile, uno script o una chiamata diretta all'API.
Infine, registra i tentativi negati per debugging e audit, ma proteggi i log. Registra chi, quale azione e quale tipo di risorsa a livello alto. Evita campi sensibili, payload completi o segreti.
Casi limite che fanno la differenza
La maggior parte dei bug di permessi emergono quando gli utenti fanno cose che il menu non prevedeva. Per questo i menu sono utili, ma solo se progettate anche i percorsi che li bypassano.
Deep link e schermate “nascoste”
Se il menu nasconde Billing per un ruolo, un utente può comunque incollare un URL salvato o aprirlo dalla cronologia del browser. Tratta ogni caricamento di pagina come una richiesta nuova: recupera i permessi correnti dell'utente e fai sì che la schermata rifiuti il caricamento di dati protetti quando manca il permesso. Un messaggio amichevole “Non hai accesso” va bene, ma la protezione reale è che il backend non restituisce nulla.
Chiamate API dirette ed endpoint bulk
Chiunque può chiamare la tua API da dev tools, uno script o un altro client. Quindi controlla i permessi su ogni endpoint, non solo sulle schermate admin. Il rischio facile da perdere sono le azioni bulk: un singolo /items/bulk-update può accidentalmente permettere a un non-admin di cambiare campi che non vedono nell'interfaccia.
I ruoli possono anche cambiare a sessione iniziata. Se un admin rimuove un permesso, l'utente potrebbe avere ancora un token vecchio o stato menu in cache. Usa token a breve scadenza o lookup dei permessi server-side e gestisci le risposte 401/403 aggiornando le capabilities e l'interfaccia.
I dispositivi condivisi creano un altro problema: lo stato del menu in cache può trapelare tra account. Memorizza la visibilità del menu indicizzata per user ID, o evita del tutto di persisterla.
Cinque test da eseguire prima del rilascio:
- Aprire un deep link a una pagina nascosta mentre si è loggati con un ruolo limitato
- Chiamare un'API protetta con una richiesta copiata da dev tools
- Cambiare il ruolo dell'utente mentre è attivo, poi riprovare azioni
- Effettuare logout, login come altro utente e verificare che il menu si resetti
- Provare endpoint bulk con campi misti permessi e non permessi
Scenario di esempio: un semplice menu CRM con enforcement reale
Immagina un CRM interno con tre ruoli: Sales, Support e Admin. Tutti fanno login e l'app mostra un menu a sinistra, ma il menu è solo una comodità. La sicurezza reale è quello che il server permette.
Ecco un semplice set di permessi che resta leggibile:
- Leads: view, create, edit, delete, export
- Tickets: view, create, edit, assign
- Billing: view, edit
- Users: view, manage
L'interfaccia inizia chiedendo al backend le azioni consentite per l'utente corrente (spesso come lista di stringhe di permesso) più contesto base come user id e team. Il menu si costruisce da quello. Se non hai billing.view, non vedi Billing. Se hai leads.export, vedi un pulsante Export nella schermata Leads. Se puoi solo modificare i tuoi lead, il pulsante Edit può comunque apparire, ma dovrebbe essere disattivato o mostrare un messaggio chiaro quando il lead non è tuo.
Ora la parte importante: ogni endpoint d'azione applica le stesse regole.
Esempio: Sales può creare lead e modificare lead che possiede. Support può visualizzare ticket e assegnarli, ma non tocca billing. Admin può gestire utenti e billing.
Quando qualcuno prova a cancellare un lead, il backend controlla:
- L'utente ha
leads.delete? - Se la regola è solo "own",
lead.owner_id == user.id? - Se il lead è bloccato (per esempio già fatturato), la cancellazione è bloccata per tutti tranne Admin?
Anche se un utente Support chiama manualmente l'endpoint delete, riceve una risposta forbidden. La voce nascosta nel menu non era mai stata la protezione. La decisione del backend lo era.
Errori comuni e trappole
La trappola più grande con i menu sensibili alle autorizzazioni è pensare di aver finito quando il menu sembra a posto. Nascondere pulsanti riduce la confusione, ma non riduce il rischio.
Errori che emergono più spesso:
- “Non visibile” diventa “non possibile.” Qualcuno può comunque chiamare l'API direttamente, riutilizzare un vecchio URL o innescare l'azione tramite dev tools. Trattate il nascondere UI come comodità, non come gate.
- Permessi controllati solo nella UI. Se il frontend decide chi può cancellare un record, avete già perso. Il backend deve essere il giudice finale per ogni azione protetta.
- Un unico flag
isAdminper tutto. È veloce all'inizio, poi si propaga. Presto ogni eccezione diventa un caso speciale e nessuno riesce più a spiegare le regole di accesso. - Fidarsi del client per dire il ruolo. Non accettate mai
role,isAdminopermissionsdal browser come verità. Derivate identity e access dal vostro token o sessione, poi lookup di ruoli e permessi server-side. - Dimenticare i permessi di lettura. I team si concentrano sulle scritture (create, update, delete), ma le perdite di dati spesso vengono dalle letture. Elenchi clienti, visualizzazione fatture, export CSV e ricerche spesso necessitano controlli.
Esempio concreto: nascondi la voce Export leads per i non-manager. Se l'endpoint di export non verifica i permessi, qualsiasi utente che indovina la richiesta (o la copia da un collega) può comunque scaricare il file.
Checklist rapida prima del rilascio
Prima di distribuire i menu sensibili alle autorizzazioni, fai un ultimo giro concentrato su cosa gli utenti possono davvero fare, non su cosa possono vedere.
Attraversa l'app come ogni ruolo principale e prova le stesse azioni. Fallo sia nell'interfaccia sia chiamando direttamente l'endpoint (o usando dev tools) per assicurarti che il server sia la fonte di verità.
Checklist:
- Per ogni azione protetta, conferma che esista un controllo di autorizzazione lato server al punto di esecuzione (non solo quando costruisci il menu).
- Fai rendere le azioni dalla UI usando capabilities fornite dal server (per esempio una can-list dalla sessione o un endpoint permessi) invece di indovinare dai nomi dei ruoli.
- Verifica che l'interfaccia gestisca risposte forbidden come uno stato normale: mostra un messaggio chiaro, mantieni la pagina stabile ed evita di lasciare l'utente in uno stato rotto.
- Testa i cambi di ruolo e permessi. Quando un admin aggiorna gli accessi, dovrebbe avere effetto rapidamente e in modo prevedibile (refresh token, invalidazione sessione o versioning dei permessi).
- Esegui alcuni test ad alto valore che coprano ruoli e azioni che toccano denaro, export, cancellazioni e impostazioni admin.
Un modo pratico per individuare gap: scegli un pulsante “pericoloso” (elimina utente, esporta CSV, cambia billing) e traccialo end-to-end. La voce di menu dovrebbe essere nascosta quando appropriato, l'API dovrebbe rifiutare chiamate non autorizzate e la UI dovrebbe recuperare con grazia quando riceve un 403.
Prossimi passi: rilasciare in sicurezza senza rallentare
Parti piccolo. Non serve una matrice di accesso perfetta al giorno uno. Scegli un gruppo ristretto di azioni che contano di più (view, create, edit, delete, export, manage users), mappale ai ruoli che già hai e procedi. Quando arriva una nuova funzionalità, aggiungi solo le nuove azioni che introduce.
Prima di costruire schermate, fai un rapido passaggio di pianificazione che elenchi azioni, non pagine. Una voce di menu come Invoices nasconde molte azioni: view list, view details, create, refund, export. Scriverle prima rende sia l'interfaccia che le regole backend più chiare e previene l'errore comune di limitare una pagina lasciando un endpoint rischioso non protetto.
Quando refattorizzi le regole di accesso, trattalo come qualsiasi altro cambiamento rischioso: tieni una rete di sicurezza. Gli snapshot ti permettono di confrontare il comportamento prima e dopo. Se un ruolo perde improvvisamente accesso o ne acquisisce di indesiderato, il rollback è più veloce che correggere in produzione mentre gli utenti sono bloccati.
Una routine di rilascio semplice aiuta i team a muoversi velocemente senza indovinare:
- Scrivi la lista di azioni per la funzionalità e nomina ogni permesso.
- Aggiungi i controlli backend prima, poi collega la visibilità UI agli stessi permessi.
- Testa un ruolo consentito e uno negato per ogni azione rischiosa.
- Registra i tentativi negati (senza dati sensibili) così puoi individuare gap.
- Scatta uno snapshot prima del rollout così puoi fare rollback rapidamente se serve.
Se costruisci con una piattaforma chat-based come Koder.ai (koder.ai), la stessa struttura vale: tieni permessi e policy definiti una sola volta, fai leggere le capabilities all'interfaccia dal server e rendi obbligatori i controlli backend in ogni handler.
Domande frequenti
What do permission-aware menus actually solve?
I menu sensibili alle autorizzazioni risolvono principalmente la chiarezza, non la sicurezza. Aiutano gli utenti a concentrarsi su ciò che possono effettivamente fare, riducono i clic morti e diminuiscono le richieste di supporto del tipo “perché vedo questa cosa?”.
La sicurezza deve comunque essere applicata nel backend, perché chiunque può provare link profondi, vecchi segnalibri o chiamate API dirette indipendentemente da quello che mostra l'interfaccia.
When should I hide a menu item vs disable it?
Nascondi quando una funzionalità dovrebbe essere effettivamente non scoperta da un ruolo e non c'è una via prevista per usarla.
Disattiva quando l'utente potrebbe avere accesso ma al momento manca il contesto, ad esempio nessun record selezionato, stato del form invalido o dati ancora in caricamento. Se disattivi, aggiungi una breve spiegazione così non sembra rotto.
Why is “hiding the button” not security?
Perché la visibilità non è autorizzazione. Un utente può incollare un URL, riutilizzare una schermata amministrativa salvata o chiamare la tua API al di fuori dell'interfaccia.
Considera l'interfaccia come guida. Considera il backend come l'ultimo decisore per ogni richiesta sensibile.
How should the frontend know what the user is allowed to do?
Il server dovrebbe restituire una piccola risposta di “capacità” dopo il login o al refresh di sessione, basata su controlli di autorizzazione lato server. L'interfaccia poi rende menu e pulsanti da quelle informazioni.
Non fidarti di flag forniti dal client come isAdmin; calcola i permessi dall'identità autenticata sul server.
What’s the simplest end-to-end way to implement permissions?
Inizia facendo l'inventario delle azioni, non delle pagine. Per ogni funzionalità, separa operazioni come read, create, update, delete, export, invite e change billing.
Poi applica ciascuna autorizzazione nell'handler backend (o in middleware/wrapper) prima di eseguire qualsiasi lavoro. Collega il menu agli stessi nomi di permesso così UI e API restano allineate.
Should I use roles or permissions as the main model?
Una regola pratica: i ruoli sono contenitori, i permessi sono la fonte di verità. Mantieni i permessi piccoli e basati su azioni (per esempio invoice.create) e assegnali ai ruoli.
Se i ruoli cominciano a moltiplicarsi per codificare condizioni (come regione o proprietà), sposta quelle condizioni nelle policy invece di creare varianti di ruolo senza fine.
How do I handle “can view but not edit” and other conditional access?
Usa le policy per regole contestuali come “modifica solo i propri record” o “approva fatture solo sotto una certa soglia”. Questo mantiene stabile la lista dei permessi e permette comunque di esprimere vincoli reali.
Il backend deve valutare la policy usando il contesto della risorsa (per esempio owner ID o org ID), non supposizioni dall'interfaccia.
Do I really need permission checks on read endpoints too?
Non sempre. Le letture che espongono dati sensibili o che bypassano i filtri normali dovrebbero essere protette, come export, log di audit, salari, liste utenti admin o qualsiasi endpoint che restituisce più di quanto l'interfaccia normalmente mostri.
Una buona regola: tutti gli scritti devono essere controllati, e anche le letture sensibili devono essere controllate.
How do I avoid security holes with bulk actions and “power” endpoints?
Gli endpoint bulk si perdono facilmente perché possono modificare molti record o campi in una sola richiesta. Un utente bloccato nella UI potrebbe comunque chiamare /items/bulk-update direttamente.
Controlla i permessi per l'azione in blocco e valida anche quali campi possono essere cambiati per quel ruolo, altrimenti potresti permettere modifiche a campi nascosti.
What should happen when roles change mid-session or the UI cache is stale?
Assumi che i permessi possano cambiare mentre qualcuno è connesso. Quando l'API restituisce 401 o 403, l'interfaccia dovrebbe gestirlo come uno stato normale: aggiorna le capacità, rinnova il menu e mostra un messaggio chiaro.
Evita anche di salvare lo stato del menu in modo che possa trapelare tra account su dispositivi condivisi; se fai cache, indicizzala per ID utente o non persisterla affatto.