Implementación de roles basada en PHP y MySQL

Una pregunta que veo a menudo:

Estoy haciendo un inicio de sesión en php. el usuario solo debe ingresar su nombre y sera re direccionado dependiendo del rol que tenga.


Estoy realizando un sistema en php y mysql quisiera saber cómo trabajar con múltiples sesiones como por ejemplo que tenga una cuenta de administrador y pueda trabajar con toda las páginas y tener cuenta de usuario que algunos vean cierta cantidad de páginas y realizar pocas funciones en el sistema


¿Cómo debo crear una sesión para el administrador? ¿En qué lugar de la aplicación se tiene que reflejar?
El usuario normal no debe ver esa parte que le corresponde al Administrador.


quiero ingresar roles de usuario en mi código pero no se como hacerlo.


Voy a crear una aplicación web para unas personas que llevan el control de unas muestras geológicas, básicamente intervienen dos grupos de usuarios el gerente del área y sus especialistas, por lo tanto no pueden tener los mismos privilegios.

La necesidad de tener diferentes secciones del sitio disponibles para diferentes roles o niveles de usuario es sumamente común.

En este artículo mostraré cómo podría implementarse un esquema como este usando PHP y MySQL.

Aclaración: en mi caso personal, para resolver este problema utilizaría Symfony, pero en este post utilizaré PHP puro para hacer la solución más generalmente aplicable

Cómo guardar los roles en la base de datos

Mi sugerencia siempre es comenzar por definir el modelo de datos.

La base de datos constituye los cimientos de cualquier aplicación web y si éstos no son sólidos, por más magia que tenga nuestro PHP, CSS, JavaScript y demás va a ser difícil terminar con algo mucho mejor que esto:

Así que… mejor pisar sobre seguro.

La primera pregunta que debés responder es: ¿cada usuario tendrá un único rol?

Si la respuesta es sí el esquema de la base de datos es bastante simple, sólo se trata de agregar un campo a la tabla users: role.

Aquí se abre una segunda bifurcación: ¿de qué tipo debe ser el campo role?

La idea más natural es crearlo como string (o tal vez enumerado).

Mi sugerencia, sin embargo, es hacer algo un poco más complicado:

  • Crear una nueva tabla roles con dos campos:
    • id
    • nombre
  • Agregar un campo role_id (en lugar de role) a la tabla users como una clave foránea a la tabla roles

En definitiva, este sería el código SQL de la creación de las tablas:

CREATE TABLE `roles` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
);

CREATE TABLE `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `email` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `role_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`),
  KEY `fk_roles` (`role_id`),
  CONSTRAINT `fk_roles` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`)
);

De esta forma el sistema está preparado para incorporar nuevos roles sin necesidad de alterar la estructura de la base (Y por lo tanto, posibilitar hacerlo desde el front-end sin intervención del desarrollador).

En el caso de que los usuarios puedan tener más de un rol simultáneamente la estructura sería un poco más compleja: deberá incluir una tabla intermedia para implementar la relación N-a-M:

-- Queda igual que en el caso anterior
CREATE TABLE `roles` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
);

-- Se elimina el campo role_id
CREATE TABLE `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `email` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`)
);

CREATE TABLE `users_roles` (
  `user_id` int(10) unsigned NOT NULL,
  `role_id` int(10) unsigned NOT NULL,
  PRIMARY KEY (`user_id`,`role_id`),
  KEY `fk_roles_2` (`role_id`),
  CONSTRAINT `fk_roles_2` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`),
  CONSTRAINT `fk_users` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
);

Para no complicar mucho el ejemplo voy a tomar el caso de un rol por usuario aunque no cambiará mucho si se trata del otro (Tal vez lo escriba en un próximo artículo).

Bien, la base de datos está, ahora es momento de ver cómo hacer el código PHP.

Cómo redireccionar al usuario según su rol

El siguiente desafío consiste en, una vez haya ingresado el usuario, redirigirlo a la sección del sitio que le corresponda según su rol.

Comencemos por un simple formulario de login:

<html>
<body>
  <form action="processLogin.php" method="post">
     <input type="text" name="username"/>
     <input type="password" name="password"/>
     <input type="submit" value="Ingresar"/>
  </form>
</body>
</html>

Y el archivo del backend

<?php

$username = $_POST['username'];
$password = $_POST['password'];

$sql = "SELECT u.id, r.name AS role, password FROM users u INNER JOIN roles r ON r.id = u.role_id WHERE username = '$username';";

// Conectar a la base de datos
// ejecutar la consulta
// $result contiene el resultado de la consulta

if (password_verify($result['password'], password_hash($password, PASSWORD_BCRYPT))) {
   $startingPage = [
      'admin' => 'admin_home.php',
      'user' => 'user_home.php',
   ];

   $nextPage = array_key_exists($result['role'], $startingPage) ? $startinPage['role'] : 'user_home.php';
   if (array_key_exists($result['role'], $startingPage)) {
      $nextPage = $startinPage[$result['role']];
   } else {
      $nextPage = $startinPage['user'];
      error_log('There is no starting page for role '.$result['role']);
   }
   session_start();
   $_SESSION['user_id'] = $result['id'];
   $_SESSION['role'] = $result['role'];
   header('Location: '.$nextPage);
} else {
   header('Location: login.html');
}

Aquí estoy usando las funciones password_verify y password_hash para hacer un login seguro.

Y con esto hemos logrado dirigir a cada persona a la sección que le corresponde…

Lo que nos falta es dejar afuera a los que quieran ir a mirar donde no les corresponde 😉

Cómo restringir el acceso a una sección del sitio según el rol del usuario

Una forma simple de restringir el acceso para alguien que no se logeó la podés leer acá.

La idea de esta sección es ampliar un poco sobre esa misma línea y permitir el acceso sólo para aquellos usuarios cuyo rol está habilitado:

 <?php

// admin_home.php

session_start();

if (!array_key_exists('user_id', $_SESSION)) {
   header('Location: login.html');
   die;
}

$allowedRoles = ['admin'];

if (!array_key_exists('role', $_SESSION) || !in_array($_SESSION['role'], $allowdRoles)) {
   header('Location: login.html');
   die;
}
?>
<h1>Bienvenido amind!</h1>

Habrás observado que el protagonista de esta pequeña novela es el array $_SESSION.

Si todavía no lo tienes super claro, te sugiero continuar leyendo por aquí.

mchojrin
Publicada el
Categorizado como Ejemplos Etiquetado como

Por mchojrin

Ayudo a desarrolladores PHP a acceder mercados y clientes más sofisticados y exigentes

6 comentarios

  1. Hola muchas gracias por tu publicación, me ha servido de mucho! Un consulta, lo quiero hacer es que cada rol tenga distintas opciones en el menu de la pagina, yo tengo creado el menu en un archivo php aparte y lo ejecuto en las paginas que voy utilizando solo necesito modificarlo de acuerdo al rol del usurio. El rol admin tendria acceso a todas las opciones del menu, los vendedores y otros tipos de usuarios, solo a una parte del menu. Gracias de nuevo!

    1. Hola Laura:

      Me alegra haberte ayudado :). ¿Cuál es la dificultad que estás encontrando? ¿No puedes armar las opciones del menú dependiendo del rol del usuario activo?

  2. Buenas tardes caballero, veo que en
    $sql = «SELECT u.id, r.name AS role, password FROM users u INNER JOIN roles r ON r.id = u.role_id WHERE username = ‘$username’;»;

    if (password_verify($result[‘password’], password_hash($password, PASSWORD_BCRYPT))) {

    Ejecutas $sql no seria mas bien $result ???

    muchas gracias

    1. Y en «WHERE username = ‘$username’;» supongo que debería ser «WHERE email = ‘$username’;» porque username no se ha creado como campo en la base de datos…

  3. Hola, excelente explicacion!! y en caso de querer manejar los permisos desde los datos de una tabla? estoy haciendo un sistema de gestion de stock donde hay varios depositos y cada deposito no deberia ver los datos del otro deposito, y hay un super usuario que puede ver todos los datos de la tabla/s

    1. Gracias Santiago, me alegra que te haya servido.

      Es un caso algo complejo el que planteas pero a grandes rasgos mi sugerencia sería:

      1 – A cada usuario vincularlo con los depósitos a los que tiene acceso. Podrías darle a cada depósito un «owner» pero me parece mejor hacerlo al revés para prevenir el caso futuro de que un usuario sea algo así como supervisor y pueda tener acceso a más de uno.
      2 – A cada usuario asignarle un rol. Si tienes un único «super» usuario, ese será el único con ese rol.
      3 – Al momento de mostrar los datos de un depósito, verificar que el usuario tenga el rol «super» o bien que el depósito actual está dentro de su jurisdicción.

      El framework Symfony maneja muy bien este tipo de situaciones, te recomiendo investigarlo un poco.

      Saludos,

¿Te quedó alguna duda? Publica aca tu pregunta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.