Los menús de navegación conscientes de permisos mejoran la claridad, pero la seguridad debe residir en el backend. Ve patrones sencillos para roles, políticas y ocultación segura en la interfaz.

Cuando la gente dice “oculta el botón”, normalmente quieren decir una de dos cosas: reducir el desorden para usuarios que no pueden usar una función, o evitar un uso indebido. Solo el primer objetivo es realista en el frontend.
Los menús conscientes de permisos son principalmente una herramienta de UX. Ayudan a que alguien abra la app y vea de inmediato lo que puede hacer, sin toparse con pantallas de “Acceso denegado” a cada rato. También reducen la carga de soporte al evitar confusiones como “¿Dónde apruebo facturas?” o “¿Por qué esta página da error?”.
Ocultar la UI no es seguridad. Es claridad.
Incluso un compañero curioso todavía puede:
Así que el problema real que resuelven los menús conscientes de permisos es dar una guía honesta. Mantienen la interfaz alineada con el trabajo, rol y contexto del usuario, y dejan claro cuando algo no está disponible.
Un buen estado final se parece a esto:
Ejemplo: en un CRM pequeño, un Sales Rep debería ver Leads y Tasks, pero no User Management. Si aun así pega la URL de gestión de usuarios, la página debería fallar cerrada y el servidor debe bloquear cualquier intento de listar usuarios o cambiar roles.
La visibilidad es lo que la interfaz decide mostrar. La autorización es lo que el sistema realmente permitirá cuando una petición llegue al servidor.
Los menús conscientes de permisos reducen la confusión. Si alguien nunca podrá ver Billing o Admin, ocultar esos elementos mantiene la app limpia y baja las solicitudes de soporte. Pero ocultar un botón no es un candado. La gente aún puede intentar el endpoint subyacente usando herramientas de desarrollador, un marcador antiguo o una petición copiada.
Una regla práctica: decide la experiencia que quieres y luego aplica la regla en el backend sin importar lo que haga la UI.
Al presentar una acción, tres patrones cubren la mayoría de los casos:
“Puedes ver pero no editar” es común y merece diseño explícito. Trátalo como dos permisos: uno para leer datos y otro para cambiarlos. En el menú podrías mostrar Detalles del cliente a todos los que pueden leer, pero solo mostrar Editar cliente a los que tienen acceso de escritura. En la página, renderiza campos como solo lectura y bloquea controles de edición, permitiendo al mismo tiempo que la página cargue.
Lo más importante: el backend decide el resultado final. Incluso si la UI oculta todas las acciones de admin, el servidor aún necesita comprobar permisos en cada petición sensible y devolver una respuesta clara de “no permitido” cuando alguien lo intenta.
La forma más rápida de entregar menús conscientes de permisos es empezar con un modelo que tu equipo pueda explicar en una frase. Si no puedes explicarlo, no lo mantendrás correcto.
Usa roles para agrupar, no para significar todo. Admin y Support son buckets útiles. Pero cuando los roles empiezan a multiplicarse (Admin-West-Coast-ReadOnly), la UI se vuelve un laberinto y el backend una suposición.
Prefiere permisos como fuente de verdad sobre lo que alguien puede hacer. Mantenlos pequeños y basados en acciones, como invoice.create o customer.export. Esto escala mejor que la proliferación de roles porque las nuevas funciones suelen añadir nuevas acciones, no nuevos títulos de trabajo.
Luego añade políticas (reglas) para el contexto. Aquí es donde manejas “puede editar solo su propio registro” o “puede aprobar facturas solo por debajo de $5,000”. Las políticas evitan crear docenas de permisos casi idénticos que difieren solo por una condición.
Una capa mantenible se ve así:
El nombre importa más de lo que se espera. Si tu UI dice Export Customers pero la API usa download_all_clients_v2, acabarás ocultando lo incorrecto o bloqueando lo correcto. Mantén nombres humanos, consistentes y compartidos entre frontend y backend:
sustantivo.verbo (o recurso.acción) consistentementeEjemplo: en un CRM, un rol Sales podría incluir lead.create y lead.update, pero una política limita las actualizaciones a leads que el usuario posee. Eso mantiene tu menú claro mientras tu backend sigue estricto.
Los menús conscientes de permisos se sienten bien porque reducen desorden y previenen clics accidentales. Pero solo ayudan cuando el backend sigue a cargo. Piensa en la UI como una pista y en el servidor como el juez.
Empieza por escribir qué estás protegiendo. No páginas, sino acciones. Ver la lista de clientes es distinto a exportar clientes o borrar un cliente. Esto es la columna vertebral de menús conscientes de permisos que no se convierten en teatro de seguridad.
canEditCustomers, canDeleteCustomers, canExport, o una lista compacta de strings de permisos. Manténlo mínimo.Una regla importante: nunca confíes en flags de roles o permisos proporcionados por el cliente. La UI puede ocultar botones según capacidades, pero la API debe rechazar peticiones no autorizadas.
Los menús conscientes de permisos deberían ayudar a las personas a encontrar lo que pueden hacer, no fingir que hacen cumplir seguridad. El frontend es una proteccióndirigida. El backend es la cerradura.
En lugar de esparcir comprobaciones de permisos por cada botón, define la navegación desde una configuración que incluya el permiso requerido para cada ítem y luego renderiza a partir de esa config. Esto mantiene las reglas legibles y evita comprobaciones olvidadas en rincones extraños de la UI.
Un patrón simple se ve así:
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));
Prefiere ocultar secciones completas (como Admin) en lugar de esparcir comprobaciones en cada enlace de página admin. Son menos lugares para equivocarse.
Oculta ítems cuando el usuario nunca podrá usarlos. Deshabilita ítems cuando el usuario tiene permiso pero falta contexto.
Ejemplo: Eliminar contacto debe estar deshabilitado hasta que se seleccione un contacto. Mismo permiso, solo falta contexto. Cuando deshabilites, añade un breve “por qué” cerca del control (tooltip, texto de ayuda o nota inline): Selecciona un contacto para eliminar.
Un conjunto de reglas que aguanta:
Ocultar items en el menú ayuda a las personas a concentrarse, pero no protege nada. El backend debe ser el juez final porque las peticiones pueden reproducirse, editarse o dispararse fuera de tu UI.
Una buena regla: cada acción que cambia datos necesita una comprobación de autorización, en un solo lugar, por el que pase toda petición. Puede ser middleware, un wrapper de handler o una pequeña capa de políticas que llames al inicio de cada endpoint. Elige un enfoque y cíñete a él o perderás caminos.
Mantén la autorización separada de la validación de entrada. Primero decide “¿puede este usuario hacer esto?” y luego valida el payload. Si validas primero, puedes filtrar detalles (como si cierto ID existe) a quien ni siquiera debería saber que la acción es posible.
Un patrón que escala:
Can(user, "invoice.delete", invoice)).Usa códigos de estado que ayuden tanto a tu frontend como a tus logs:
401 Unauthorized cuando la llamada no está autenticada.403 Forbidden cuando está autenticada pero no tiene permiso.Ten cuidado con usar 404 Not Found como disfraz. Puede servir para no revelar que un recurso existe, pero si lo mezclas aleatoriamente, depurar se vuelve doloroso. Elige una regla consistente por tipo de recurso.
Asegúrate de que la misma autorización se ejecute si la acción vino de un clic, una app móvil, un script o una llamada directa a la API.
Finalmente, registra intentos denegados para depuración y auditoría, pero mantén los logs seguros. Registra quién, qué acción y qué tipo de recurso a alto nivel. Evita campos sensibles, payloads completos o secretos.
La mayoría de bugs de permisos aparecen cuando los usuarios hacen algo que tu menú nunca esperó. Por eso los menús conscientes de permisos son útiles, pero solo si diseñas también para las rutas que los evaden.
Si el menú oculta Billing para un rol, un usuario aún puede pegar una URL guardada o abrirla desde el historial. Trata cada carga de página como una petición nueva: obtén los permisos actuales del usuario y haz que la pantalla misma se niegue a cargar datos protegidos cuando falte el permiso. Un mensaje amable de “No tienes acceso” está bien, pero la protección real es que el backend no devuelve nada.
Cualquiera puede llamar a tu API desde dev tools, un script o otro cliente. Así que comprueba permisos en cada endpoint, no solo en pantallas admin. El riesgo fácil de pasar por alto son las acciones masivas: un /items/bulk-update puede permitir accidentalmente que un no-admin cambie campos que nunca ven en la UI.
Los roles también pueden cambiar en mitad de sesión. Si un admin quita un permiso, el usuario puede seguir con un token viejo o estado de menú en caché. Usa tokens de corta vida o una consulta de permisos en servidor y maneja respuestas 401/403 refrescando permisos y actualizando la UI.
Los dispositivos compartidos crean otro problema: el estado de visibilidad en caché puede filtrarse entre cuentas. Almacena la visibilidad del menú indexada por user ID, o evita persistirla por completo.
Cinco pruebas útiles antes del lanzamiento:
Imagina un CRM interno con tres roles: Sales, Support y Admin. Todos inician sesión y la app muestra un menú a la izquierda, pero el menú es solo una conveniencia. La seguridad real está en lo que el servidor permite.
Aquí hay un conjunto simple de permisos que sigue siendo legible:
La UI empieza pidiendo al backend las acciones permitidas del usuario actual (a menudo como una lista de strings de permisos) además de contexto básico como user id y equipo. El menú se construye desde eso. Si no tienes billing.view, no ves Billing. Si tienes leads.export, ves un botón Export en la pantalla de Leads. Si solo puedes editar tus propios leads, el botón Edit aún puede aparecer pero debe deshabilitarse o mostrar un mensaje claro cuando el lead no es tuyo.
Ahora la parte importante: cada endpoint de acción aplica las mismas reglas.
Ejemplo: Sales puede crear leads y editar leads que posee. Support puede ver tickets y asignarlos, pero no tocar facturación. Admin puede gestionar usuarios y facturación.
Cuando alguien intenta borrar un lead, el backend comprueba:
leads.delete?lead.owner_id == user.id?Aunque un Support llame manualmente al endpoint de eliminación, recibe una respuesta forbidden. El elemento oculto del menú nunca fue la protección; la decisión fue del backend.
La mayor trampa es pensar que terminaste el trabajo cuando el menú se ve correcto. Ocultar botones reduce confusión, pero no reduce el riesgo.
Errores que aparecen con más frecuencia:
isAdmin para todo. Parece rápido y luego se propaga. Pronto cada excepción es un caso especial y nadie puede explicar las reglas de acceso.role, isAdmin o permissions del navegador como verdad. Deriva identidad y acceso desde tu propia sesión o token y luego consulta roles y permisos en el servidor.Un ejemplo concreto: ocultas Export leads para no managers. Si el endpoint de exportación no comprueba permisos, cualquier usuario que adivine la petición (o la copie de un compañero) puede descargar el archivo.
Antes de publicar menús conscientes de permisos, haz una última pasada centrada en lo que los usuarios pueden hacer realmente, no en lo que pueden ver. Recorre tu app como cada rol principal y prueba las mismas acciones. Hazlo en la UI y también llamando al endpoint directamente (o usando dev tools) para asegurarte de que el servidor es la fuente de verdad.
Checklist:
Una forma práctica de detectar huecos: elige un botón “peligroso” (eliminar usuario, exportar CSV, cambiar facturación) y traza su flujo de punta a punta. El ítem del menú debe ocultarse cuando corresponda, la API debe rechazar llamadas no autorizadas y la UI debe recuperarse con gracia al recibir un 403.
Empieza pequeño. No necesitas una matriz de acceso perfecta el día uno. Elige el puñado de acciones que más importan (ver, crear, editar, borrar, exportar, gestionar usuarios), asígnalas a los roles que ya tienes y sigue adelante. Cuando llegue una nueva función, añade solo las acciones nuevas que introduce.
Antes de construir pantallas, haz una pasada de planificación que liste acciones, no páginas. Un ítem de menú como Invoices oculta muchas acciones: ver lista, ver detalles, crear, devolver, exportar. Escribir eso primero hace que tanto la UI como las reglas del backend sean más claras y evita el error común de proteger una página completa mientras dejas un endpoint riesgoso sin protección.
Cuando refactorices reglas de acceso, trátalo como cualquier otro cambio riesgoso: mantén una red de seguridad. Los snapshots te permiten comparar comportamiento antes y después. Si un rol de repente pierde acceso necesario o gana acceso que no debe, hacer rollback es más rápido que arreglar en caliente con usuarios bloqueados.
Una rutina de lanzamiento simple ayuda a los equipos a moverse rápido sin adivinar:
Si construyes con una plataforma basada en chat como Koder.ai (koder.ai), la misma estructura aplica: define permisos y políticas una vez, deja que la UI lea capacidades del servidor y haz que las comprobaciones backend sean obligatorias en cada handler.
Los menús conscientes de permisos principalmente resuelven claridad, no seguridad. Ayudan a los usuarios a centrarse en lo que realmente pueden hacer, reducen clics que llevan a callejones sin salida y disminuyen las preguntas de soporte tipo “¿por qué veo esto?”.
La seguridad debe seguir aplicándose en el backend, porque cualquiera puede intentar enlaces profundos, marcadores antiguos o llamadas directas a la API sin importar lo que muestre la UI.
Oculta cuando una función debería ser prácticamente indescrubrible para un rol y no existe una vía esperada para que la usen.
Deshabilita cuando el usuario podría tener acceso pero le falta contexto en ese momento, por ejemplo, cuando no hay ningún registro seleccionado, el formulario está inválido o los datos aún se están cargando. Si deshabilitas, añade una breve explicación para que no parezca roto.
Porque visibilidad no es autorización. Un usuario puede pegar una URL, reutilizar una pantalla de administrador guardada o llamar a tu API fuera de la UI.
Trata la UI como guía. Trata al backend como el decisor final para cada petición sensible.
Tu servidor debería devolver una pequeña respuesta de “capacidades” después del inicio de sesión o la actualización de sesión, basada en comprobaciones de permisos en el servidor. La UI renderiza menús y botones a partir de eso.
No confíes en flags proporcionados por el cliente como isAdmin desde el navegador; calcula permisos desde la identidad autenticada en el servidor.
Empieza inventariando acciones, no páginas. Para cada característica separa cosas como ver, crear, actualizar, borrar, exportar, invitar y cambiar facturación.
Luego aplica cada permiso en el handler del backend (o en middleware/wrapper) antes de hacer trabajo. Vincula el menú a los mismos nombres de permiso para que UI y API permanezcan alineados.
Por defecto práctico: los roles son buckets y los permisos son la fuente de verdad. Mantén los permisos pequeños y basados en acciones (por ejemplo, invoice.create) y asígnalos a roles.
Si los roles empiezan a multiplicarse para codificar condiciones (como región u ownership), mueve esas condiciones a políticas en lugar de crear variantes infinitas de roles.
Usa políticas para reglas contextuales como “puede editar solo su propio registro” o “puede aprobar facturas solo bajo cierto importe”. Así mantienes estable la lista de permisos mientras expresas restricciones del mundo real.
El backend debe evaluar la política usando el contexto del recurso (por ejemplo, owner ID u org ID), no supuestos desde la UI.
No siempre. Las lecturas que exponen datos sensibles o que evaden el filtrado normal también deben estar protegidas, por ejemplo exportes, logs de auditoría, datos salariales, listas de usuarios admin o cualquier endpoint que devuelva más de lo que la UI normalmente muestra.
Una buena regla: todas las escrituras deben comprobarse, y las lecturas sensibles también.
Los endpoints bulk son fáciles de pasar por alto porque pueden cambiar muchos registros o campos en una sola petición. Un usuario bloqueado en la UI puede aún llamar a /items/bulk-update directamente.
Comprueba permisos para la acción bulk en sí y valida qué campos se permiten cambiar para ese rol; de lo contrario podrías permitir editar campos ocultos accidentalmente.
Asume que los permisos pueden cambiar mientras alguien está conectado. Cuando la API devuelve 401 o 403, la UI debería manejarlo como un estado normal: refrescar capacidades, actualizar el menú y mostrar un mensaje claro.
Además, evita persistir la visibilidad del menú de forma que pueda filtrarse entre cuentas en dispositivos compartidos; si la cachéas, clavea por identidad de usuario o no la persistas.