Menus sensíveis a permissões melhoram a clareza, mas a segurança precisa viver no backend. Veja padrões simples para roles, policies e esconder a UI com segurança.

Quando as pessoas dizem “ocultar o botão”, normalmente querem uma de duas coisas: reduzir poluição para usuários que não podem usar um recurso, ou impedir uso indevido. Só o primeiro objetivo é realista no frontend.
Menus sensíveis a permissões são principalmente uma ferramenta de UX. Eles ajudam alguém a abrir o app e ver imediatamente o que pode fazer, sem trombar em telas de “Acesso negado” a cada clique. Também reduzem carga no suporte, evitando confusões como “Onde aprovo faturas?” ou “Por que esta página deu erro?”
Ocultar a UI não é segurança. É clareza.
Mesmo um colega curioso ainda pode:
Então o problema real que menus sensíveis a permissões resolvem é orientação honesta. Eles mantêm a interface alinhada com o trabalho, papel e contexto do usuário, deixando claro quando algo não está disponível.
Um bom estado final parece com isto:
Exemplo: em um CRM pequeno, um Sales Rep deve ver Leads e Tasks, mas não User Management. Se ele colar a URL de user management na barra mesmo assim, a página deve falhar fechada, e o servidor deve bloquear qualquer tentativa de listar usuários ou alterar papéis.
Visibilidade é o que a interface escolhe mostrar. Autorização é o que o sistema realmente permite quando uma requisição chega ao servidor.
Menus sensíveis a permissões reduzem confusão. Se alguém nunca terá acesso a Billing ou Admin, ocultar esses itens mantém o app limpo e reduz tickets de suporte. Mas ocultar um botão não é um cadeado. As pessoas ainda podem tentar o endpoint subjacente usando dev tools, um bookmark antigo ou uma requisição copiada.
Uma regra prática: decida qual experiência quer oferecer e, então, aplique a regra no backend independentemente do que a UI fizer.
Ao decidir como apresentar uma ação, três padrões cobrem a maioria dos casos:
“Você pode ver mas não pode editar” é comum e vale a pena desenhar explicitamente. Trate como duas permissões: uma para leitura e outra para alteração. No menu, você pode mostrar Customer details para todos que têm leitura, mas só mostrar Edit customer para quem tem acesso de escrita. Na página, renderize campos como somente leitura e bloqueie controles de edição, enquanto ainda permite o carregamento da página.
Mais importante: o backend decide o resultado final. Mesmo que a UI oculte toda ação de admin, o servidor ainda precisa checar permissões em cada requisição sensível e retornar uma resposta clara de “não permitido” quando alguém tentar.
A forma mais rápida de entregar menus sensíveis a permissões é começar com um modelo que sua equipe consiga explicar em uma frase. Se você não consegue explicar, não vai manter correto.
Use roles para agrupar, não para dar significado. Admin e Support são buckets úteis. Mas quando roles começam a proliferar (Admin-West-Coast-ReadOnly), a UI vira um labirinto e o backend vira adivinhação.
Prefira permissões como fonte de verdade sobre o que alguém pode fazer. Mantenha-as pequenas e orientadas a ações, como invoice.create ou customer.export. Isso escala melhor que a proliferação de roles porque novos recursos normalmente adicionam novas ações, não novos cargos.
Depois, adicione policies (regras) para contexto. É aqui que você trata “pode editar apenas seu próprio registro” ou “pode aprovar faturas só abaixo de $5.000.” Políticas evitam criar dezenas de permissões quase-duplicadas que diferem apenas por uma condição.
Uma camada que dá para manter fica assim:
Nomear importa mais do que se espera. Se sua UI diz Export Customers mas a API usa download_all_clients_v2, você acabará ocultando a coisa errada ou bloqueando a certa. Mantenha nomes humanos, consistentes e compartilhados entre frontend e backend:
noun.verb (ou resource.action) de forma consistenteExemplo: em um CRM, um papel Sales pode incluir lead.create e lead.update, mas uma policy limita updates a leads que o usuário possui. Isso mantém seu menu claro enquanto o backend fica rígido.
Menus sensíveis a permissões dão uma boa experiência porque reduzem poluição e evitam cliques acidentais. Mas só ajudam se o backend mantiver o controle. Pense na UI como uma dica e no servidor como o juiz.
Comece registrando o que você está protegendo. Não páginas, mas ações. Ver lista de clientes é diferente de exportar clientes e diferente de deletar cliente. Isso é a espinha dorsal de menus sensíveis a permissões que não viram teatro de segurança.
canEditCustomers, canDeleteCustomers, canExport, ou uma lista compacta de strings de permissão. Mantenha mínimo.Uma regra pequena mas importante: nunca confie em roles ou flags enviadas pelo cliente. A UI pode ocultar botões com base nas capacidades, mas a API ainda deve rejeitar requisições não autorizadas.
Menus sensíveis a permissões devem ajudar as pessoas a encontrar o que podem fazer, não fingir que impõem segurança. O frontend é um trilho; o backend é o cadeado.
Em vez de espalhar checagens por cada botão, defina sua navegação a partir de uma config que inclua a permissão requerida para cada item, e então renderize a partir dessa config. Isso mantém as regras legíveis e evita checagens esquecidas em cantos estranhos da UI.
Um padrão simples:
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));
Prefira ocultar seções inteiras (como Admin) a espalhar checagens em cada link de admin. São menos pontos para errar.
Oculte itens quando o usuário nunca terá permissão. Desative itens quando o usuário tem permissão, mas falta contexto.
Exemplo: Delete contact deve estar desativado até que um contato seja selecionado. Mesma permissão, só contexto insuficiente. Quando desativar, adicione uma breve mensagem explicativa (tooltip, texto auxiliar ou nota inline): Selecione um contato para deletar.
Um conjunto de regras que funciona:
Ocultar itens ajuda foco, mas não protege nada. O backend precisa ser o juiz final porque requisições podem ser replayed, editadas ou disparadas fora da UI.
Uma boa regra: toda ação que altera dados precisa de uma checagem de autorização, em um único lugar, que toda requisição passe. Pode ser middleware, um wrapper de handler ou uma pequena camada de policy chamada no início de cada endpoint. Escolha uma abordagem e mantenha-a, ou caminhos serão perdidos.
Mantenha autorização separada da validação de entrada. Primeiro decida: “este usuário pode fazer isto?”, depois valide o payload. Se validar antes, você pode vazar detalhes (como quais IDs existem) para alguém que nem deveria saber que a ação é possível.
Um padrão escalável:
Can(user, "invoice.delete", invoice)).Use códigos de status que ajudem tanto a frontend quanto seus logs:
401 Unauthorized quando o chamador não está logado.403 Forbidden quando está logado mas não autorizado.Tenha cuidado com 404 Not Found como disfarce. Pode ser útil para não revelar a existência de um recurso, mas se misturar aleatoriamente complica o debug. Escolha uma regra consistente por tipo de recurso.
Assegure que a mesma autorização rode independente de a ação vir de um clique, app móvel, script ou chamada direta à API.
Finalmente, registre tentativas negadas para debug e auditoria, mas proteja logs. Registre quem, qual ação e qual tipo de recurso em alto nível. Evite campos sensíveis, payloads completos ou segredos.
A maioria dos bugs de permissão aparece quando usuários fazem algo que o menu nunca previu. Por isso menus sensíveis a permissões são úteis, mas só se você projetar também para os caminhos que os contornam.
Se o menu oculta Billing para um papel, um usuário ainda pode colar uma URL salva ou abrir a partir do histórico. Trate cada carregamento de página como uma requisição nova: busque as permissões atuais do usuário e faça a tela recusar carregar dados protegidos quando faltar permissão. Uma mensagem amigável de “Você não tem acesso” é aceitável, mas a proteção real é o backend retornar vazio.
Qualquer um pode chamar sua API de dev tools, script ou outro cliente. Então cheque permissões em todo endpoint, não só nas telas de admin. O risco fácil de perder são endpoints em massa: um único /items/bulk-update pode permitir que um não-admin altere campos que nunca vê na UI.
Papéis também podem mudar durante a sessão. Se um admin remove uma permissão, o usuário pode ainda ter um token antigo ou estado de menu em cache. Use tokens de curta duração ou lookup server-side das permissões, e trate 401/403 atualizando capacidades e ajustando a UI.
Dispositivos compartilhados criam outro risco: estado do menu em cache pode vazar entre contas. Armazene visibilidade do menu com chave do ID do usuário, ou evite persistir.
Cinco testes que valem a pena antes do release:
Imagine um CRM interno com três papéis: Sales, Support e Admin. Todos fazem login e o app mostra um menu à esquerda, mas o menu é só conveniência. A segurança real é o que o servidor permite.
Aqui está um conjunto simples de permissões legível:
A UI começa pedindo ao backend as ações que o usuário atual pode fazer (geralmente como lista de strings de permissão) mais contexto básico como user id e time. O menu é construído a partir disso. Se você não tem billing.view, não vê Billing. Se tem leads.export, vê um botão Export na tela de Leads. Se pode editar apenas seus leads, o botão Edit ainda pode aparecer, mas deve ficar desativado ou mostrar uma mensagem clara quando o lead não for seu.
Agora o ponto importante: cada endpoint de ação aplica as mesmas regras.
Exemplo: Sales pode criar leads e editar leads que possui. Support pode ver tickets e atribuir tickets, mas não mexa em billing. Admin pode gerenciar usuários e billing.
Quando alguém tenta deletar um lead, o backend checa:
leads.delete?lead.owner_id == user.id?Mesmo se um Support chamar manualmente o endpoint de delete, recebe forbidden. O item oculto no menu nunca foi a proteção. A decisão do backend foi.
A maior armadilha é achar que o trabalho acabou quando o menu parece certo. Ocultar botões reduz confusão, mas não reduz risco.
Erros mais comuns:
isAdmin para tudo. Parece rápido, depois se espalha. Em pouco tempo cada exceção vira caso especial e ninguém entende as regras.role, isAdmin ou permissions vindos do navegador como verdade. Derive identidade e acesso da sua sessão ou token, e busque roles/permissões server-side.Exemplo concreto: você oculta o menu Export leads para não-gerentes. Se o endpoint de export não checar permissões, qualquer usuário que adivinhar a requisição (ou a copiar de um colega) ainda baixa o arquivo.
Antes de enviar menus sensíveis a permissões, faça uma passada final focada no que usuários realmente podem fazer, não no que podem ver.
Percorra seu app como cada papel principal e tente o mesmo conjunto de ações. Faça isso na UI e também chamando o endpoint diretamente (ou usando dev tools) para garantir que o servidor é a fonte de verdade.
Checklist:
Uma forma prática de achar lacunas: escolha um botão “perigoso” (deletar usuário, exportar CSV, alterar cobrança) e trace-o do início ao fim. O item do menu deve ficar oculto quando apropriado, a API deve rejeitar chamadas não autorizadas e a UI deve se recuperar bem ao receber um 403.
Comece pequeno. Você não precisa de uma matriz de acesso perfeita no dia 1. Escolha o punhado de ações que mais importam (view, create, edit, delete, export, manage users), mapeie para os papéis que já existem e siga em frente. Quando um novo recurso chegar, adicione só as novas ações que ele introduz.
Antes de construir telas, faça um rápido planejamento listando ações, não páginas. Um item de menu como Invoices esconde muitas ações: ver lista, ver detalhes, criar, reembolsar, exportar. Escrevê-las primeiro deixa UI e regras backend mais claras e evita o erro comum de proteger uma página inteira enquanto deixa um endpoint arriscado desprotegido.
Ao refatorar regras de acesso, trate como qualquer mudança arriscada: mantenha uma rede de segurança. Snapshots permitem comparar comportamento antes e depois. Se um papel perder acesso que precisa, ou ganhar acesso indevido, reverter é mais rápido do que consertar produção enquanto usuários ficam bloqueados.
Uma rotina simples de rollout ajuda times a avançar rápido sem chutar no escuro:
Se você está construindo com uma plataforma baseada em chat como Koder.ai (koder.ai), a mesma estrutura se aplica: mantenha permissões e policies definidas uma vez, faça a UI ler capacidades do servidor e torne checagens backend não opcionais em cada handler.
Menus sensíveis a permissões resolvem na maior parte das vezes clareza, não segurança. Eles ajudam os usuários a focar no que realmente podem fazer, reduzem cliques que terminam em páginas de erro e cortam perguntas ao suporte como “onde aprovo faturas?”.
A segurança ainda precisa ser aplicada no backend, porque qualquer pessoa pode tentar links diretos, bookmarks antigos ou chamadas diretas à API independentemente do que a interface mostra.
Oculte quando um recurso deve ser praticamente indisponível para um papel e não houver caminho esperado para que esse usuário o utilize.
Desative quando o usuário pode ter acesso, mas falta contexto no momento — por exemplo, nenhum registro selecionado, formulário inválido ou dados ainda carregando. Se desativar, mostre uma explicação curta para não parecer quebrado.
Porque visibilidade não é autorização. Um usuário pode colar uma URL, reutilizar um bookmark de admin ou chamar sua API fora da UI.
Trate a interface como orientação. Trate o backend como o árbitro final para toda requisição sensível.
Seu servidor deve retornar um pequeno payload de “capacidades” depois do login ou ao atualizar a sessão, baseado em checagens de permissão no servidor. A UI então renderiza menus e botões a partir disso.
Não confie em flags vindas do cliente como isAdmin; calcule permissões a partir da identidade autenticada no servidor.
Comece inventariando ações, não páginas. Para cada recurso separe: ler, criar, atualizar, excluir, exportar, convidar, alterar cobrança, etc.
Depois, aplique cada permissão no handler do backend (ou via middleware/ wrapper) antes de qualquer trabalho. Ligue o menu aos mesmos nomes de permissão para manter UI e API alinhadas.
Um padrão prático é: papéis (roles) são agrupamentos, permissões são a fonte de verdade. Mantenha permissões pequenas e orientadas a ações (por exemplo, invoice.create) e anexe-as aos papéis.
Se os papéis começarem a multiplicar para codificar condições (região, leitura apenas), mova essas condições para políticas em vez de criar variações infinitas de papéis.
Use políticas para regras contextuais como “editar somente seu próprio registro” ou “aprovar faturas abaixo de um limite”. Isso mantém a lista de permissões estável enquanto expressa restrições do mundo real.
O backend deve avaliar a política usando o contexto do recurso (por exemplo, owner ID ou org ID), não suposições da UI.
Nem sempre. Leituras que expõem dados sensíveis ou que pulam filtros normais também devem ser restritas, como exports, logs de auditoria, salários ou qualquer endpoint que retorne mais do que a UI normalmente mostra.
Boa regra: todas as escritas precisam ser checadas, e leituras sensíveis também.
Endpoints em massa são fáceis de esquecer porque podem alterar muitos registros ou campos numa única requisição. Verifique permissões para a ação de bulk e valide quais campos aquele papel pode alterar, caso contrário você pode permitir a edição de campos que a UI esconde.
Presuma que permissões podem mudar enquanto alguém está logado. Quando a API retornar 401 ou 403, a UI deve tratar isso como um estado normal: atualizar capacidades, ajustar o menu e mostrar uma mensagem clara.
Também evite persistir estado do menu de forma que possa vazar entre contas em dispositivos compartilhados; se cachear, associe ao ID do usuário ou não persista.