Machtigingsbewuste navigatiemenu's verbeteren de duidelijkheid, maar beveiliging moet op de backend plaatsvinden. Bekijk eenvoudige patronen voor rollen, policies en veilig verbergen in de UI.

Als mensen zeggen “verberg de knop”, bedoelen ze meestal één van twee dingen: rommel verminderen voor gebruikers die een functie niet kunnen gebruiken, of misbruik stoppen. Alleen het eerste doel is realistisch aan de frontend.
Machtigingsbewuste navigatiemenu's zijn vooral een UX-hulpmiddel. Ze helpen iemand de app te openen en direct te zien wat hij kan doen, zonder voortdurend op “Toegang geweigerd”-schermen te lopen. Ze verlagen ook de supportdruk door verwarring te voorkomen zoals “Waar keur ik facturen goed?” of “Waarom geeft deze pagina een fout?”.
UI verbergen is geen beveiliging. Het is duidelijkheid.
Zelfs een nieuwsgierige collega kan nog steeds:
Het echte probleem dat machtigingsbewuste menu's oplossen is eerlijke begeleiding. Ze houden de interface in lijn met iemands taak, rol en context, en maken het duidelijk wanneer iets niet beschikbaar is.
Een goed eindresultaat ziet er zo uit:
Voorbeeld: in een kleine CRM zou een Salesmedewerker Leads en Tasks moeten zien, maar geen User Management. Als ze toch de user management-URL plakken, moet de pagina gesloten falen en de server elke poging om gebruikers te tonen of rollen te wijzigen blokkeren.
Zichtbaarheid is wat de interface kiest te tonen. Autorisatie is wat het systeem daadwerkelijk toelaat wanneer een verzoek de server bereikt.
Machtigingsbewuste menu's verminderen verwarring. Als iemand nooit Billing of Admin mag zien, houdt het verbergen van die items de app overzichtelijk en verlaagt het supporttickets. Maar een knop verbergen is geen slot. Mensen kunnen nog steeds het onderliggende endpoint proberen met devtools, een oude bladwijzer of een gekopieerd verzoek.
Een praktische regel: beslis welke ervaring je wilt, en handhaaf die regel op de backend ongeacht wat de UI doet.
Als je beslist hoe je een actie presenteert, dekken drie patronen de meeste gevallen:
“Je kunt bekijken maar niet bewerken” komt vaak voor en is het waard expliciet te ontwerpen. Behandel het als twee permissies: één voor lezen en één voor wijzigen. In het menu kun je Customer details tonen aan iedereen die kan lezen, maar alleen Edit customer tonen aan degenen met schrijfrechten. Op de pagina rendert je velden read-only en tref je bewerkcontrols af, terwijl de pagina toch laadt.
Belangrijkst: de backend beslist het uiteindelijke resultaat. Zelfs als de UI elk admin-actie verbergt, moet de server permissies checken op elk gevoelig verzoek en een duidelijke “niet toegestaan” response teruggeven wanneer iemand het probeert.
De snelste manier om machtigingsbewuste menu's uit te rollen is starten met een model dat je team in één zin kan uitleggen. Als je het niet kunt uitleggen, houd je het niet correct.
Gebruik rollen om te groeperen, niet voor betekenis. Admin en Support zijn nuttige buckets. Maar wanneer rollen gaan vermenigvuldigen (Admin-West-Coast-ReadOnly), wordt de UI een doolhof en de backend giswerk.
Geef de voorkeur aan permissies als bron van waarheid voor wat iemand kan doen. Houd ze klein en actie-gebaseerd, zoals invoice.create of customer.export. Dit schaalt beter dan rol-sprawl omdat nieuwe features meestal nieuwe acties toevoegen, niet nieuwe functietitels.
Voeg daarna policies (regels) toe voor context. Hier behandel je “kan alleen je eigen record bewerken” of “kan facturen goedkeuren alleen onder $5.000”. Policies voorkomen dat je tientallen bijna-duplicaat permissies maakt die alleen door een conditie verschillen.
Een onderhoudbare laagopbouw ziet er zo uit:
Naamgeving is belangrijker dan verwacht. Als je UI zegt Export Customers maar de API gebruikt download_all_clients_v2, zul je uiteindelijk het verkeerde verbergen of het juiste blokkeren. Houd namen menselijk, consistent en gedeeld tussen frontend en backend:
noun.verb (of resource.action)Voorbeeld: in een CRM kan een Sales-rol lead.create en lead.update bevatten, maar een policy beperkt updates tot leads die de gebruiker bezit. Dat houdt je menu duidelijk terwijl de backend strikt blijft.
Machtigingsbewuste menu's voelen goed omdat ze rommel verminderen en toevallige klikken voorkomen. Maar ze helpen alleen als de backend aan het roer blijft. Zie de UI als hint, en de server als rechter.
Begin met op te schrijven wat je beschermt. Niet pagina's, maar acties. Klantlijst bekijken is anders dan klanten exporteren of verwijderen. Dit is het fundament van machtigingsbewuste navigatiemenu's die niet in security theater veranderen.
canEditCustomers, canDeleteCustomers, canExport, of een compacte lijst met permissiestrings. Houd het minimaal.Een kleine maar belangrijke regel: vertrouw nooit op door de cliënt geleverde rol- of permissievlaggen. De UI kan knoppen verbergen op basis van capabilities, maar de API moet nog steeds ongeautoriseerde verzoeken afwijzen.
Machtigingsbewuste navigatiemenu's moeten mensen helpen vinden wat ze kunnen doen, niet doen alsof ze beveiliging afdwingen. De frontend is een guide rail. De backend is het slot.
In plaats van permissiechecks over elke knop te verspreiden, definieer je navigatie vanuit één config die de vereiste permissie voor elk item bevat, en render je vanaf die config. Dit houdt de regels leesbaar en voorkomt vergeten checks op vreemde plekken in de UI.
Een simpel patroon ziet er zo uit:
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));
Geef de voorkeur aan het verbergen van hele secties (zoals Admin) boven het verspreiden van checks op elke admin-paginalink. Dat zijn minder plekken om iets fout te doen.
Verberg items wanneer de gebruiker er nooit toestemming voor heeft. Deactiveer items wanneer de gebruiker toestemming heeft, maar de huidige context ontbreekt.
Voorbeeld: Delete contact moet gedeactiveerd zijn totdat een contact is geselecteerd. Zelfde permissie, alleen niet genoeg context. Wanneer je deactiveert, voeg een korte “waarom” boodschap toe bij de knop (tooltip, helpertekst of inline opmerking): Selecteer een contact om te verwijderen.
Een regelset die standhoudt:
Menu-items verbergen helpt mensen focussen, maar beschermt niets. De backend moet de uiteindelijke rechter zijn omdat verzoeken kunnen worden gereplayed, aangepast of getriggerd buiten je UI.
Een goede regel: elke actie die data verandert heeft één autorisatiecheck, op één plek, die elk verzoek passeert. Dat kan middleware zijn, een handler-wrapper of een klein policy-layer dat je aan het begin van elk endpoint aanroept. Kies één aanpak en houd je eraan, anders mis je paden.
Houd autorisatie los van inputvalidatie. Beslis eerst: “mag deze gebruiker dit doen?”, daarna valideer je de payload. Als je eerst valideert, kun je details lekken (zoals welke record IDs bestaan) aan iemand die niet eens zou mogen weten dat de actie mogelijk is.
Een patroon dat schaalt:
Can(user, "invoice.delete", invoice)).Gebruik statuscodes die zowel je frontend als je logs helpen:
401 Unauthorized wanneer de aanroeper niet ingelogd is.403 Forbidden wanneer ingelogd maar niet toegestaan.Wees voorzichtig met 404 Not Found als vermomming. Het kan nuttig zijn om te voorkomen dat een resource bestaat onthuld wordt, maar als je het willekeurig mixt wordt debuggen lastig. Kies een consistente regel per resource-type.
Zorg dat dezelfde autorisatie loopt of de actie nu vanuit een knop, een mobiele app, een script of een directe API-call komt.
Log geweigerde pogingen voor debugging en audits, maar houd logs veilig. Leg vast wie, welke actie en welk hoog-niveau resourcetype. Vermijd gevoelige velden, volledige payloads of secrets.
De meeste permissiefouten verschijnen wanneer gebruikers iets doen dat je menu nooit verwachtte. Daarom zijn machtigingsbewuste menu's nuttig, maar alleen als je ook ontwerpt voor paden die ze omzeilen.
Als het menu Billing verbergt voor een rol, kan een gebruiker nog steeds een opgeslagen URL plakken of deze openen uit de browsergeschiedenis. Behandel elke paginalaad als een vers verzoek: haal de huidige gebruikerspermissies op en laat het scherm zelf beschermde data weigeren te laden wanneer de permissie ontbreekt. Een vriendelijke “Je hebt geen toegang” boodschap is prima, maar de echte bescherming is dat de backend niets teruggeeft.
Iedereen kan je API aanroepen vanuit devtools, een script of een andere client. Controleer dus permissies op elk endpoint, niet alleen admin-schermen. Het makkelijk te missen risico zijn bulk-acties: één /items/bulk-update kan per ongeluk een niet-admin toestaan velden te wijzigen die ze nooit in de UI zien.
Rollen kunnen ook midden in een sessie veranderen. Als een admin een permissie verwijdert, kan de gebruiker nog een oud token of gecachte menustatus hebben. Gebruik kortlevende tokens of een server-side permissie-lookup, en behandel 401/403 responses door permissies te verversen en de UI bij te werken.
Gedeelde apparaten vormen een andere valkuil: gecachte menustoestanden kunnen tussen accounts lekken. Sla menuzichtbaarheid op keyed by user ID, of vermijd het persistente opslaan.
Vijf tests die het waard zijn om te draaien voor release:
Stel je een interne CRM voor met drie rollen: Sales, Support en Admin. Iedereen logt in en de app toont een linker menu, maar het menu is slechts een gemak. De echte veiligheid is wat de server toestaat.
Hier is een eenvoudige permissieset die leesbaar blijft:
De UI begint met de backend te vragen welke acties de huidige gebruiker mag (vaak als een lijst permissiestrings) plus basiscontext zoals user id en team. Het menu wordt daarvan opgebouwd. Als je billing.view niet hebt, zie je Billing niet. Als je leads.export hebt, zie je een Export-knop op het Leads-scherm. Als je alleen je eigen leads mag bewerken, kan de Edit-knop toch verschijnen, maar die moet gedeactiveerd zijn of een duidelijke boodschap tonen wanneer de lead niet van jou is.
Nu het belangrijkste: elk action-endpoint handhaaft dezelfde regels.
Voorbeeld: Sales kan leads aanmaken en leads bewerken die ze bezitten. Support kan tickets bekijken en toewijzen, maar mag Billing niet aanraken. Admin kan gebruikers en billing beheren.
Als iemand probeert een lead te verwijderen, controleert de backend:
leads.delete?lead.owner_id == user.id?Zelfs als een Support-gebruiker handmatig het delete-endpoint aanroept, krijgt die een forbidden-response. Het verborgen menu-item was nooit de bescherming. De backend-beslissing was dat.
De grootste valkuil met machtigingsbewuste menu's is denken dat je klaar bent als het menu er goed uitziet. Knoppen verbergen vermindert verwarring, maar vermindert het risico niet.
Veelvoorkomende fouten:
isAdmin vlag voor alles. Het voelt snel, maar verspreidt zich. Binnenkort wordt elke uitzondering een speciale case en kan niemand de toegangsregels uitleggen.role, isAdmin of permissions uit de browser als waarheid. Leid identiteit en toegang af uit je eigen sessie of token, en zoek rollen/permissies server-side op.Een concreet voorbeeld: je verbergt de Export leads-menuoptie voor niet-managers. Als het export-endpoint permissies niet checkt, kan elke gebruiker die het verzoek raadt (of van een collega kopieert) het bestand nog steeds downloaden.
Voordat je machtigingsbewuste menu's live zet, doe een laatste ronde die focust op wat gebruikers daadwerkelijk kunnen doen, niet alleen wat ze kunnen zien. Loop je app door als elke hoofdrol en probeer dezelfde acties. Doe het in de UI en ook door het endpoint direct aan te roepen (of met browser devtools) om zeker te zijn dat de server de bron van waarheid is.
Checklist:
Een praktische manier om lacunes te spotten: kies één “gevaarlijke” knop (delete user, export CSV, wijzig billing) en traceer die end-to-end. Het menu-item moet verborgen zijn wanneer gepast, de API moet ongeautoriseerde calls afwijzen, en de UI moet netjes herstellen als hij een 403 ontvangt.
Begin klein. Je hoeft niet op dag één een perfect toegangsmatrix te hebben. Kies een handvol acties die het meest belangrijk zijn (view, create, edit, delete, export, manage users), map ze naar de rollen die je al hebt en ga verder. Wanneer een nieuwe feature komt, voeg alleen de nieuwe acties toe die het introduceert.
Voordat je schermen bouwt, doe een korte planningsronde waarin je acties opschrijft, niet pagina's. Een menu-item als Invoices verbergt veel acties: lijst bekijken, details bekijken, aanmaken, terugbetalen, exporteren. Die eerst opschrijven maakt zowel de UI als de backendregels duidelijker en voorkomt de fout een hele pagina te beveiligen maar een riskant endpoint onderbemand te laten.
Als je toegangregels refactort, behandel het als elke andere risicovolle wijziging: houd een vangnet. Snapshots laten je gedrag vóór en ná vergelijken. Als een rol plots toegang verliest die het nodig heeft, of toegang krijgt die het niet zou moeten, is terugrollen sneller dan production hot-fixes terwijl gebruikers geblokkeerd zijn.
Een eenvoudige release-routine helpt teams snel te bewegen zonder te gokken:
Als je bouwt met een chat-gebaseerd platform zoals Koder.ai (koder.ai), geldt dezelfde structuur: houd permissies en policies eenmaal gedefinieerd, laat de UI capabilities van de server lezen, en maak backend-checks niet-optioneel in elke handler.
Machtigingsbewuste menu's lossen vooral duidelijkheid op, niet beveiliging. Ze helpen gebruikers te focussen op wat ze daadwerkelijk kunnen doen, verminderen doelloze klikken en verlagen ondersteuningsvragen als “waar kan ik dit goedkeuren?”.
Beveiliging moet nog steeds op de backend worden afgedwongen, omdat iedereen diepe links, oude bladwijzers of directe API-aanroepen kan proberen ongeacht wat de UI toont.
Verberg wanneer een functie effectief onvindbaar moet zijn voor een rol en er geen verwachte weg is voor hen om die te gebruiken.
Deactiveer wanneer de gebruiker mogelijk toegang heeft maar momenteel context mist, zoals geen geselecteerd record, een ongeldige formulierstatus of nog ladende data. Als je deactiveert, geef een korte uitleg zodat het niet kapot lijkt.
Omdat zichtbaarheid geen autorisatie is. Een gebruiker kan een URL plakken, een gebookmarkte admin-pagina hergebruiken of je API buiten je UI om aanroepen.
Zie de UI als begeleiding. Zie de backend als de uiteindelijke beslisser voor elke gevoelige aanvraag.
Je server moet een klein “capabilities” antwoord teruggeven na login of bij sessie-refresh, gebaseerd op server-side permissiecontroles. De UI rendert vervolgens menu's en knoppen aan de hand daarvan.
Vertrouw niet op cliënt-geleverde vlaggen zoals isAdmin van de browser; bereken permissies op basis van de geauthenticeerde identiteit op de server.
Begin met het inventariseren van acties, niet pagina's. Voor elke feature: read, create, update, delete, export, invite en billing-wijzigingen. Handhaaf elke actie in de backend-handler (of middleware/wrapper) voordat je werk uitvoert. Koppel het menu aan diezelfde permissienamen zodat UI en API aligned blijven.
Een praktisch uitgangspunt: rollen zijn groepen, permissies zijn de bron van waarheid. Houd permissies klein en actie-gebaseerd (bijvoorbeeld invoice.create) en koppel ze aan rollen.
Als rollen vermenigvuldigen om voorwaarden te encoderen (zoals regio of eigendom), verplaats die voorwaarden naar policies in plaats van eindeloze rolvarianten te maken.
Gebruik policies voor contextuele regels zoals “alleen je eigen record bewerken” of “facturen goedkeuren tot een limiet”. Dat houdt je permissielijst stabiel terwijl je echte wereldbeperkingen uitdrukt.
De backend evalueert de policy met resource-context (zoals owner ID of org ID), niet op aannames uit de UI.
Niet altijd. Reads die gevoelige data tonen of normale filtering omzeilen moeten ook afgeschermd worden, zoals exports, auditlogs, salarisgegevens, admin-userlijsten, of elk endpoint dat meer teruggeeft dan de UI normaal laat zien.
Als vuistregel: alle writes moeten gecontroleerd worden, en gevoelige reads ook.
Bulk-endpoints zijn makkelijk te missen omdat ze veel records of velden in één verzoek kunnen wijzigen. Een gebruiker kan in de UI geblokkeerd zijn maar /items/bulk-update direct aanroepen.
Controleer permissies voor de bulk-actie zelf en valideer welke velden voor die rol mogen worden gewijzigd, anders kun je per ongeluk verborgen velden laten bewerken.
Ga ervan uit dat permissies kunnen veranderen terwijl iemand ingelogd is. Als de API 401 of 403 teruggeeft, moet de UI dat als normale staat behandelen: ververs capabilities, werk het menu bij en toon een duidelijke boodschap.
Bewaar menuzichtbaarheid niet op een manier die kan lekken tussen accounts op gedeelde apparaten; als je het cachet, koppel het aan de gebruikersidentiteit of sla het helemaal niet persistent op.