Menu nawigacyjne uwzględniające uprawnienia poprawiają czytelność, ale bezpieczeństwo musi być na backendzie. Zobacz proste wzorce dla ról, polityk i bezpiecznego ukrywania w UI.

Kiedy ktoś mówi „ukryj przycisk”, zwykle ma na myśli jedną z dwóch rzeczy: zmniejszyć bałagan dla użytkowników, którzy nie mogą korzystać z funkcji, albo powstrzymać nadużycia. Tylko pierwszy cel jest realistyczny po stronie frontend.
Menu świadome uprawnień to głównie narzędzie UX. Pomagają komuś otworzyć aplikację i od razu zobaczyć, co może robić, bez trafiania na „Access denied” co drugi klik. Zmniejszają też obciążenie supportu, bo unikamy pytań typu „Gdzie zatwierdzam faktury?” albo „Dlaczego ta strona się wywala?”.
Ukrywanie UI to nie zabezpieczenie. To jasność.
Nawet ciekawski współpracownik może:
Prawdziwy problem, który rozwiązują menu świadome uprawnień, to szczere wskazówki. Trzymają interfejs zgodny z rolą i kontekstem użytkownika, jednocześnie jasno pokazując, gdy coś jest niedostępne.
Dobry stan końcowy wygląda tak:
Przykład: w małym CRM, Sales Rep powinien widzieć Leads i Tasks, ale nie User Management. Jeśli mimo to wklei URL zarządzania użytkownikami, strona powinna się zamknąć (fail closed), a serwer powinien zablokować każde próby listowania użytkowników czy zmiany ról.
Widoczność to to, co interfejs decyduje się pokazać. Autoryzacja to to, co system faktycznie pozwoli, gdy żądanie trafi na serwer.
Menu świadome uprawnień zmniejsza zamieszanie. Jeśli ktoś nigdy nie powinien zobaczyć Billing lub Admin, ukrycie tych pozycji utrzymuje aplikację w porządku i zmniejsza liczbę zgłoszeń do supportu. Ale ukrycie przycisku nie jest zamkiem. Ludzie wciąż mogą spróbować wywołać endpoint używając dev tools, starego bookmarka lub skopiowanego żądania.
Praktyczna zasada: zdecyduj, jakie doświadczenie chcesz zapewnić, a potem egzekwuj regułę na backendzie bez względu na to, co robi UI.
Gdy decydujesz, jak zaprezentować akcję, trzy wzorce pokrywają większość przypadków:
„Możesz przeglądać, ale nie edytować” jest częstym przypadkiem i warto projektować go jawnie. Traktuj jako dwie uprawnienia: jedno do czytania, jedno do zmiany. W menu możesz pokazać szczegóły klienta każdemu, kto ma prawa do odczytu, ale pokazywać Edytuj klienta tylko tym, którzy mają zapis. Na stronie renderuj pola w trybie tylko do odczytu i blokuj kontrolki edycji, ale pozwól stronie się załadować.
Najważniejsze: backend decyduje o wyniku końcowym. Nawet jeśli UI ukryje wszystkie akcje admina, serwer musi sprawdzać uprawnienia przy każdym wrażliwym żądaniu i zwracać jasne „not allowed”, gdy ktoś spróbuje.
Najszybszy sposób na wdrożenie menu świadomych uprawnień to start z modelem, który zespół potrafi wyjaśnić jednym zdaniem. Jeśli nie potrafisz tego wytłumaczyć, nie utrzymasz go poprawnie.
Używaj ról do grupowania, nie do znaczenia. Admin i Support to przydatne kubełki. Ale gdy role zaczynają się mnożyć (Admin-West-Coast-ReadOnly), UI zamienia się w labirynt, a backend w zgadywankę.
Preferuj uprawnienia jako źródło prawdy co do tego, co ktoś może zrobić. Trzymaj je małe i oparte na akcjach, np. invoice.create lub customer.export. To skaluje się lepiej niż rozrost ról, bo nowe funkcje zwykle dodają nowe akcje, a nie nowe tytuły stanowisk.
Potem dodaj polityki (rules) dla kontekstu. Tu obsługujesz „może edytować tylko własny rekord” albo „może zatwierdzać faktury tylko poniżej 5 000”. Polityki zapobiegają tworzeniu kilkudziesięciu niemal identycznych uprawnień różniących się tylko warunkiem.
Utrzymywalna warstwowa struktura wygląda tak:
Nazewnictwo ma większe znaczenie, niż się wydaje. Jeśli UI mówi Export Customers, a API używa download_all_clients_v2, prędzej czy później ukryjesz złe rzeczy albo zablokujesz te, które trzeba. Trzymaj nazwy czytelne dla ludzi, spójne i współdzielone między frontendem a backendem:
noun.verb (lub resource.action)Przykład: w CRM rola Sales może mieć lead.create i lead.update, ale polityka ogranicza aktualizacje do leadów, których właścicielem jest użytkownik. To utrzymuje menu czytelne, a backend surowy.
Menu świadome uprawnień działają dobrze, bo redukują bałagan i zapobiegają przypadkowym kliknięciom. Ale pomagają tylko wtedy, gdy backend pozostaje w kontroli. Traktuj UI jako wskazówkę, a serwer jako sędziego.
Zacznij od spisania tego, co chronisz. Nie stron, lecz akcji. Lista klientów jest inna niż eksport klientów czy usunięcie klienta. To kręgosłup menu, które nie stanie się tylko „teatrem bezpieczeństwa”.
canEditCustomers, canDeleteCustomers, canExport, lub kompaktową listę permission strings. Trzymaj to minimalne.Mała, ale ważna zasada: nigdy nie ufaj pochodzącym z klienta flagom roli czy uprawnień. UI może ukrywać przyciski na podstawie capabilities, ale API wciąż musi odrzucać nieautoryzowane żądania.
Menu świadome uprawnień powinno pomagać ludziom znaleźć, co mogą robić, a nie udawać, że egzekwuje zabezpieczenia. Frontend jest prowadnicą; backend jest zamkiem.
Zamiast rozsypywać sprawdzenia uprawnień po każdym przycisku, zdefiniuj nawigację w jednym configu, który zawiera wymagane uprawnienie dla każdej pozycji, a potem renderuj z tego configu. To utrzymuje reguły czytelne i zmniejsza ryzyko zapomnianych sprawdzeń w rzadkich częściach UI.
Prosty wzorzec wygląda tak:
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));
Wol preferować ukrywanie całych sekcji (np. Admin) niż rozsypywanie sprawdzeń przy każdym linku do admina. Mniej miejsc do popełnienia błędu.
Ukrywaj elementy, gdy użytkownik nigdy nie będzie mógł ich użyć. Wyłączaj elementy, gdy użytkownik ma uprawnienie, ale brakuje kontekstu.
Przykład: Usuń kontakt powinien być wyłączony, dopóki nie wybrano kontaktu. To to samo uprawnienie, po prostu brakuje kontekstu. Gdy wyłączasz, dodaj krótkie „dlaczego” obok kontrolki (tooltip, pomocniczy tekst lub notka): Wybierz kontakt, aby usunąć.
Zasady, które się sprawdzają:
Ukrycie pozycji w menu pomaga skupić uwagę, ale nic nie zabezpiecza. Backend musi być ostatecznym sędzią, bo żądania można powtarzać, edytować lub wywołać spoza UI.
Dobra zasada: każda akcja zmieniająca dane potrzebuje jednego miejsca sprawdzenia autoryzacji, przez które przechodzi każde żądanie. Może to być middleware, wrapper handlera lub mała warstwa polityk, którą wywołujesz na początku każdego endpointu. Wybierz jedną metodę i się jej trzymaj, inaczej pominiesz ścieżki.
Trzymaj autoryzację oddzielnie od walidacji wejścia. Najpierw zadaj pytanie: „czy ten użytkownik może to zrobić?”, potem waliduj payload. Jeśli walidujesz najpierw, możesz ujawnić szczegóły (np. że dany ID istnieje) komuś, kto w ogóle nie powinien wiedzieć, że akcja jest możliwa.
Wzorzec, który skaluje się dobrze:
Can(user, "invoice.delete", invoice)).Używaj kodów statusu, które pomagają frontendowi i logom:
401 Unauthorized gdy wywołujący nie jest zalogowany.403 Forbidden gdy jest zalogowany, ale nie ma uprawnień.Uważaj z 404 Not Found jako maskowaniem. Może być przydatne, by nie ujawniać istnienia zasobu, ale jeśli mieszasz to losowo, debugowanie stanie się trudne. Wybierz spójną regułę per typ zasobu.
Upewnij się, że ta sama autoryzacja uruchamia się niezależnie od tego, czy akcja przyszła z kliknięcia przycisku, aplikacji mobilnej, skryptu czy bezpośredniego wywołania API.
Na koniec, loguj odmowy w celach debugowania i audytu, ale trzymaj logi bezpieczne. Rejestruj kto, jaka akcja i jaki ogólny typ zasobu. Unikaj zapisów wrażliwych pól, pełnych payloadów czy sekretów.
Większość błędów z uprawnieniami pojawia się, gdy użytkownicy robią coś, czego menu nie przewidziało. Dlatego menu są użyteczne, ale tylko jeśli zaprojektujesz ścieżki, które je omijają.
Jeśli menu ukrywa Billing dla danej roli, użytkownik nadal wklei zapisany URL lub otworzy go z historii przeglądarki. Traktuj każde ładowanie strony jak świeże żądanie: pobierz aktualne uprawnienia użytkownika i nie pozwól ekranowi ładować chronionych danych, gdy brak uprawnienia. Przyjazny komunikat „Nie masz dostępu” jest OK, ale realną ochroną jest, że backend nic nie zwróci.
Każdy może wywołać twoje API z dev tools, skryptu lub innego klienta. Sprawdzaj uprawnienia na każdym endpointzie, nie tylko na ekranach admina. Łatwo przeoczyć ryzyko akcji masowych: pojedynczy /items/bulk-update może pozwolić nie-adminowi zmieniać pola, których w UI nie widzi.
Role mogą też zmieniać się w trakcie sesji. Jeśli admin odbiera uprawnienie, użytkownik może mieć nadal stary token lub zcache’owane menu. Używaj tokenów krótkotrwałych lub server-side lookup uprawnień i obsłuż 401/403 odświeżeniem uprawnień i aktualizacją UI.
Współdzielone urządzenia to kolejna pułapka: zcache’owany stan menu może przeciekać między kontami. Przechowuj widoczność menu kluczem user ID lub w ogóle jej nie persistuj.
Pięć testów wartch uruchomienia przed wydaniem:
Wyobraź sobie wewnętrzny CRM z trzema rolami: Sales, Support i Admin. Wszyscy się logują i aplikacja pokazuje lewy pasek menu, ale menu to tylko wygoda. Rzeczywiste bezpieczeństwo to, co serwer pozwala.
Oto prosty zestaw uprawnień, który pozostaje czytelny:
UI zaczyna od zapytania backendu o dozwolone akcje aktualnego użytkownika (często jako lista permission strings) oraz podstawowy kontekst jak user id i zespół. Menu budowane jest z tego. Jeśli nie masz billing.view, nie widzisz Billing. Jeśli masz leads.export, widzisz przycisk Export na ekranie Leads. Jeśli możesz edytować tylko swoje leady, przycisk Edit może się pojawić, ale powinien być wyłączony lub pokazywać jasny komunikat, gdy lead nie należy do ciebie.
Teraz ważna część: każdy endpoint akcji egzekwuje te same reguły.
Przykład: Sales może tworzyć leady i edytować leady, których jest właścicielem. Support może przeglądać tickety i przypisywać je, ale nie może dotykać billing. Admin może zarządzać użytkownikami i billingiem.
Gdy ktoś próbuje usunąć lead, backend sprawdza:
leads.delete?lead.owner_id == user.id?Nawet jeśli Support ręcznie wywoła endpoint usunięcia, dostanie forbidden. Ukryte menu nigdy nie było ochroną — decyzję podjął backend.
Największa pułapka to myślenie, że zadanie jest zakończone, gdy menu wygląda poprawnie. Ukrycie przycisków zmniejsza zamieszanie, ale nie zmniejsza ryzyka.
Najczęstsze błędy:
isAdmin dla wszystkiego. Szybko się rozprzestrzenia. Wkrótce każda wyjątek to osobny przypadek, a nikt nie potrafi wyjaśnić zasad dostępu.role, isAdmin ani permissions z przeglądarki jako prawdy. Wyprowadzaj tożsamość i dostęp z własnej sesji lub tokena, potem sprawdzaj role/uprawnienia po stronie serwera.Konkretny przykład: ukrywasz Export leads dla nie-managerów. Jeśli endpoint eksportu nie sprawdza uprawnień, każdy, kto zgadnie żądanie (lub skopiuje je od kolegi), może pobrać plik.
Zanim wypuścisz menu świadome uprawnień, zrób ostatni przegląd skupiony na tym, co użytkownicy faktycznie mogą zrobić, a nie tylko na tym, co widzą.
Przejdź aplikację jako każda główna rola i wykonaj ten sam zestaw akcji. Zrób to w UI i także wywołując endpointy bezpośrednio (lub korzystając z dev tools), aby upewnić się, że serwer jest źródłem prawdy.
Checklist:
Praktyczny sposób na znalezienie luk: wybierz jeden „niebezpieczny” przycisk (usuń użytkownika, eksport CSV, zmiana billing) i prześledź go end-to-end. Element menu powinien być ukryty gdy trzeba, API powinno odrzucić nieautoryzowane wywołania, a UI powinno się ładnie odzyskać przy 403.
Zacznij od małego zakresu. Nie potrzebujesz idealnej matrycy dostępu na dzień pierwszy. Wybierz kilka najważniejszych akcji (view, create, edit, delete, export, manage users), przypisz je do ról, i idź dalej. Gdy pojawi się nowa funkcja, dodaj tylko nowe akcje, które wprowadza.
Zanim zaczniesz budować ekran, zrób krótką sesję planowania i wypisz akcje, nie strony. Pozycja menu jak Invoices kryje wiele akcji: view list, view details, create, refund, export. Spisanie tego wcześniej ułatwia pracę UI i backendowi oraz zapobiega błędowi polegającemu na zablokowaniu strony przy pozostawieniu ryzykownego endpointu bez ochrony.
Gdy refaktoryzujesz reguły dostępu, traktuj to jak ryzykowną zmianę: miej siatkę bezpieczeństwa. Snapshoty pozwalają porównać zachowanie przed i po. Jeśli rola nagle straci dostęp, lub zyska niepowinna mieć, rollback jest szybszy niż hot-fix w produkcji, gdy użytkownicy są zablokowani.
Proste zasady wydawnicze, które pomagają szybko działać bez zgadywania:
Jeśli budujesz z platformą chatową jak Koder.ai (koder.ai), ta sama struktura nadal obowiązuje: definiuj uprawnienia i polityki raz, pozwól UI czytać capabilities z serwera i wymuszaj sprawdzenia po stronie serwera w każdym handlerze.
Menu świadome uprawnień w większości przypadków rozwiązują czytelność, nie bezpieczeństwo. Pomagają użytkownikom skupić się na tym, co mogą zrobić, zmniejszają liczbę niepotrzebnych kliknięć i ograniczają pytania do supportu typu „dlaczego to widzę?”.
Bezpieczeństwo wciąż musi być egzekwowane po stronie serwera, bo każdy może spróbować wejść przez bezpośredni link, stary bookmark lub wywołać API niezależnie od tego, co pokazuje UI.
Ukrywaj, gdy funkcja powinna być praktycznie nieodkrywalna dla danej roli i nie ma oczekiwanej ścieżki jej użycia.
Wyłączaj, gdy użytkownik może mieć dostęp, ale brakuje mu teraz kontekstu — np. nie wybrano rekordu, formularz jest nieprawidłowy lub dane się ładują. Jeśli wyłączasz, dołącz krótkie wyjaśnienie, by kontrolka nie wyglądała na zepsutą.
Bo widoczność to nie to samo co autoryzacja. Użytkownik może wkleić URL, otworzyć zapisany ekran admina albo wywołać API poza interfejsem.
Traktuj UI jako wskazówkę. Traktuj backend jako ostatecznego sędziego dla każdej wrażliwej operacji.
Twój serwer powinien zwracać niewielką odpowiedź z „capabilities” po zalogowaniu lub odświeżeniu sesji, opartą na serwerowych sprawdzeniach uprawnień. UI renderuje menu i przyciski na podstawie tego ładunku.
Nie ufaj flagom pochodzącym z przeglądarki, jak isAdmin; oblicz uprawnienia na serwerze z uwierzytelnioną tożsamością.
Zacznij od spisu akcji, nie stron. Dla każdej funkcji rozbij operacje na read, create, update, delete, export, invite, zmiany billingowe itp.
Następnie egzekwuj każdą akcję w backendowym handlerze (lub middleware) zanim zostanie wykonana. Podłącz menu do tych samych nazw uprawnień, aby UI i API pozostały spójne.
Praktyczna zasada: role to wiadra, uprawnienia to źródło prawdy. Trzymaj uprawnienia małe i oparte na akcjach (np. invoice.create) i przypinaj je do ról.
Jeśli role zaczynają mnożyć się, by zakodować warunki (region, tylko do odczytu), przenieś te warunki do polityk zamiast tworzyć kolejne warianty ról.
Użyj polityk do reguł kontekstowych, takich jak „edytuj tylko własny rekord” albo „zatwierdzaj faktury poniżej progu”. Dzięki temu lista uprawnień pozostaje stabilna, a backend ocenia reguły na podstawie kontekstu zasobu (np. owner_id czy org_id).
Nie zawsze. Ochrona odczytów jest konieczna, gdy dane są wrażliwe lub gdy endpoint omija normalne filtrowanie — np. exporty, logi audytu, dane płacowe, lista użytkowników admina itp.
Dobra zasada: wszystkie zapisy muszą być sprawdzane, a wrażliwe odczyty też powinny być chronione.
Endpointy masowe łatwo przeoczyć, bo pojedyncze wywołanie może zmienić wiele rekordów lub pól. Użytkownik może być zablokowany w UI, ale wciąż wywołać /items/bulk-update bezpośrednio.
Sprawdzaj uprawnienia dla akcji masowej i dodatkowo waliduj, które pola dana rola może zmieniać, by nie dopuścić do edycji ukrytych pól.
Zakładaj, że uprawnienia mogą się zmienić w trakcie sesji. Przy 401/403 UI powinno obsłużyć to jak normalny stan: odświeżyć capabilities, zaktualizować menu i wyświetlić jasny komunikat.
Nie zapisuj widoczności menu w sposób, który może przeciekać między kontami na współdzielonych urządzeniach; jeśli cache’ujesz, kluczkuj go identyfikatorem użytkownika lub w ogóle go nie persistuj.