Tema
Protección de rutas por rol
Casi todo panel tiene varios tipos de usuario: el admin ve todo, el editor gestiona contenido y el cliente sólo su perfil. Este snippet añade funciones para restringir páginas, botones y acciones según el rol, más una versión con permisos finos.
Opción 1: roles simples (columna rol)
Usa la columna rol de la tabla usuarios del snippet de autenticación.
php
<?php
require_once __DIR__ . '/auth.php';
/** Jerarquía: un rol superior hereda lo de los inferiores */
const NIVELES_ROL = [
'cliente' => 1,
'editor' => 2,
'admin' => 3,
];
/** ¿El usuario actual tiene alguno de estos roles? */
function tiene_rol(string ...$roles): bool
{
$u = usuario();
return $u && in_array($u['rol'], $roles, true);
}
/** ¿El usuario tiene al menos este nivel? (editor pasa si se pide 'cliente') */
function nivel_minimo(string $rol): bool
{
$u = usuario();
return $u && (NIVELES_ROL[$u['rol']] ?? 0) >= (NIVELES_ROL[$rol] ?? PHP_INT_MAX);
}
/** Corta la ejecución con 403 si no tiene el rol */
function requiere_rol(string ...$roles): array
{
$u = requiere_login(); // primero, que haya sesión
if (!in_array($u['rol'], $roles, true)) {
http_response_code(403);
include __DIR__ . '/../errores/403.php';
exit;
}
return $u;
}
/** Variante por nivel */
function requiere_nivel(string $rol): array
{
$u = requiere_login();
if (!nivel_minimo($rol)) {
http_response_code(403);
include __DIR__ . '/../errores/403.php';
exit;
}
return $u;
}
/** Comprueba que el registro pertenezca al usuario (o que sea admin) */
function requiere_dueno(int $usuarioIdDelRegistro): void
{
$u = requiere_login();
if ($u['rol'] !== 'admin' && $u['id'] !== $usuarioIdDelRegistro) {
http_response_code(403);
exit('No tienes permiso para ver este recurso.');
}
}php
<!DOCTYPE html>
<html lang="es">
<head><meta charset="UTF-8"><title>Acceso denegado</title></head>
<body style="font-family:system-ui;text-align:center;padding:60px">
<h1>403 — Acceso denegado</h1>
<p>Tu cuenta no tiene permisos para ver esta página.</p>
<a href="/panel.php">Volver al panel</a>
</body>
</html>Uso en páginas
php
<?php
// admin/usuarios.php — sólo administradores
require __DIR__ . '/../includes/roles.php';
$u = requiere_rol('admin');
// admin/posts.php — editores y administradores
$u = requiere_rol('admin', 'editor');
// o con jerarquía:
$u = requiere_nivel('editor');
// pedidos/ver.php — cada cliente ve sólo sus pedidos
$stmt = db()->prepare('SELECT * FROM pedidos WHERE id = ?');
$stmt->execute([(int) $_GET['id']]);
$pedido = $stmt->fetch() ?: exit('No existe');
requiere_dueno((int) $pedido['usuario_id']);Mostrar u ocultar elementos del menú
php
<nav>
<a href="/panel.php">Inicio</a>
<?php if (nivel_minimo('editor')): ?>
<a href="/admin/posts.php">Publicaciones</a>
<?php endif; ?>
<?php if (tiene_rol('admin')): ?>
<a href="/admin/usuarios.php">Usuarios</a>
<a href="/admin/ajustes.php">Ajustes</a>
<?php endif; ?>
</nav>Importante
Ocultar un botón no es seguridad. Si alguien conoce la URL admin/usuarios.php puede entrar directo. La comprobación con requiere_rol() debe estar en cada página y en cada acción POST, no sólo en el menú.
Opción 2: permisos finos (roles ↔ permisos)
Cuando el cliente pide cosas como "el vendedor puede ver pedidos pero no borrarlos", conviene pasar a permisos. Usa el esquema del snippet Usuarios, roles y permisos y estas funciones:
php
<?php
require_once __DIR__ . '/auth.php';
/** Carga (una sola vez por petición) los permisos del usuario actual */
function permisos_usuario(): array
{
static $cache = null;
if ($cache !== null) return $cache;
$u = usuario();
if (!$u) return $cache = [];
$stmt = db()->prepare(
'SELECT DISTINCT p.clave
FROM usuario_rol ur
JOIN rol_permiso rp ON rp.rol_id = ur.rol_id
JOIN permisos p ON p.id = rp.permiso_id
WHERE ur.usuario_id = ?'
);
$stmt->execute([$u['id']]);
return $cache = $stmt->fetchAll(PDO::FETCH_COLUMN);
}
function puede(string $permiso): bool
{
return in_array($permiso, permisos_usuario(), true);
}
function autorizar(string $permiso): void
{
requiere_login();
if (!puede($permiso)) {
http_response_code(403);
exit('No tienes permiso: ' . htmlspecialchars($permiso));
}
}php
<?php
// pedidos/eliminar.php
require __DIR__ . '/../includes/permisos.php';
autorizar('pedidos.eliminar');
// En la vista
if (puede('pedidos.editar')) { echo '<a href="editar.php?id=5">Editar</a>'; }Cómo integrarlo
- Ten la autenticación funcionando.
- Elige la opción 1 (rápida) o la 2 (flexible). Puedes empezar con la 1 y migrar después.
- Pon la comprobación en la primera línea de cada archivo protegido.
Buena práctica
Nombra los permisos como recurso.accion (productos.crear, usuarios.ver). Son fáciles de leer, de buscar en el código y de agrupar en una pantalla de administración.