Skip to content

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 ​

  1. Ten la autenticación funcionando.
  2. Elige la opción 1 (rápida) o la 2 (flexible). Puedes empezar con la 1 y migrar después.
  3. 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.

Academia Web — aprende, copia y construye.