Manejando usuarios con PHP Sessions y MySQL – Parte II

Segunda y última parte de este mini tutorial acerca del manejo de usuarios con PHP. en esta parte hablaremos acerca del control de acceso mediante el uso de sesiones. Ojota que no es difícil, pero una buena práctica nos hace con un sistema robusto y anti-intrusiones (bueh!, aunque sea un poquito más seguro).

Controlando el acceso.

El próximo paso es construir el sitio en sí, y mientras que pedir el par usuario/contraseña y corroborarlos con lo existente en la BBDD (base de datos) es bastante lineal y fácil, el desafío más grande es hacerlo en tal forma que el usuario necesite sólo loguearse una vez en la visita al sitio. Y para esto, el manejo de sesiones que trae PHP es perfecto.

 

Sesiones en PHP

Si nunca usaste el soporte de sesiones que implementa PHP desde su versión 4.0, podés inclusive estar navegando enlo que estoy hablando. Más allá de lo complicado que nos parezcan, las sesiones son un método muy simple de crear variables que se quedan “andando” mientras el usuario permanezca en el sitio. Salvo que las configuremos de otra forma, una sesión trabaja automáticamente estableciendo una cookie en el navegador del usuario, conteniendo un “session ID” (identificador de sesión), el cual es un largo string que sirve y existe con el sólo propósito de identificar al usuario en el sitio mientras dure su visita. El navegador envía esa cookie con cada interacción cliente-servidor, o sea, con cada request que se hace desde el usuario al server. Usando un set de varios archivos temporales, el servidor de PHP lleva cuenta de las variables que han sido registradas en cada sesión y sus valores asociados.

Antes de continuar, necesitamos verificar que el archivo php.ini haya sido configurado correctamente en nuestro servidor web. En este, dentro de la sección marcada como [Session] veintialgo de opciones que comienzan  con la palabra session, y aunque la mayoría de ellas deben quedar como están, hay algunas cruciales que tenemos que ver:

session.save_handler  = files
session.save_path     = "C:WINDOWSTEMP"
session.use_cookies   = 1

session.save_path le dice a PHP donde crear los archivos temporales que usará para llevar la cuenta de las sesiones. Debe ser direccionada a un directorio existente y con los permisos respectivos, o te va a aparecer un error en cada una de las páginas del sitio… Dentro de Unix, /tmp es la opción más utilizada, mientras que en Windows por lo general se usa  c:/WindowsTemp.

OJOTA: Reiniciar siempre el servidor web cuando se hacen este tipo de cambios.

Ahora ya estamos listos, y lo primero que debemos saber es cómo le decimos a PHP que empieza a buscar los datos de sesion… simplemente con la sentencia session_start. Si PHP encuentra que ya existe un ID de sesión asociado cuando la función es llamada, entonces PHP restaura dichas variables que pertenecen a la misma

session_start();

Para especificarle a PHP que variable de las guardadas en la sesion nos interesa, simplemente la seteamos en el arreglo de $_SESSION. Por ejemplo, si queremos guardar un valor en una variable pwd lo haremos así:

$_SESSION['pwd'] = valor;

Y para remover esta variable usamos la función unset:

unset($_SESSION['pwd']);

Finalmente, si queremos limpiar la sesión actual y que no queden rastros, entonces deberemos borrar el arreglo $_SESSION  y usar session_destroy:

$_SESSION = array();
session_destroy();

Para info un poco más detallada, dense una vuelta por el manual oficial: Session handling functions.

El Script de Control de Acceso

Para cada una de las página debemos tener encuenta el siguiente flujo de acceso:

La primera vez que una página protegida es pedida, el usuario aún no ha ingresado sus credenciales. El script detcta esto y muestra la pantalla de logueo. Una vez se envién esos datos, la página se recargará y se verifcarán si ambos datos son correctos en la BBDD (o sea, si es un usuario registrado y si la contraseña es correcta). Si todo va bien, se guardan las variables sesion y se muestra la página pedida, en el caso que algo no funque de acuerdoa  nuestras especificaciones, se mostrará una página de acceso denegado.

Lo maravilloso es que este proceso es idéntico para todas las páginas, permitiendo la creación de una función común que se incluirá directamente con la línea:

<?php include 'accesscontrol.php'; ?>

Por lo que a su vez, dicho archivo irá conteniendo:

<?php // accesscontrol.php
  include_once 'common.php';
  include_once 'db.php';

Se usa  include_once en vez de  include sólo por si el archivo principal también incluye a estos. Si common.php fuera incluído dos veces, por ejemplo, PHP dará un warning de que la función fue declarada más de una vez.

Luego, llamamos a session_start para o bien abrir una nueva sesión o utilizar los datos declarados anteriormente.

 session_start();

Ya en este punto, los detalles del login del usuario deberían estar disponibles desde el formulario de ingreso o desde los datos guardados en el arreglo de variables de la sesion.  Pero, yendo por partes, el script precisará sacar dichas credenciales desde  $_POST  o $_SESSION :

$uid = isset($_POST['uid']) ? $_POST['uid'] : $_SESSION['uid'];
$pwd = isset($_POST['pwd']) ? $_POST['pwd'] : $_SESSION['pwd'];

Ambas lineas son operadores ternarios, que toman la forma:

condition ? value_if_true : value_if_false

Si condition es verdadera, la expresión será igualada a  value_if_true. Si no, será igual a  value_if_false.

Entonces, entendiendo la primer linea, veremos que si uid existe en el arreglo  $_POST, de la manera (isset($_POST['uid'])), $uid obtendrá el valor de  $_POST['uid']. Si no, obtendrá el valor de  $_SESSION['uid']. Lo mismo se analiza con $pwd.

Si no estamos cómodos con este operador ternario, podemos volver al if comunacho:

if (isset($_POST['uid']) {
  $uid = $_POST['uid'];
 } else {
  $uid = $_SESSION['uid'];
}
if (isset($_POST['pwd']) {
  $pwd = $_POST['pwd'];
 } else {
  $pwd = $_SESSION['pwd'];
}

Como sepuede ver, el operador ternario nos salva de escribir de más. Nos volvemos más productivos.

Bien, ya para esta instancia, el único caso enque no existan ninguno de los datos, es cuando aún no han sido ingresados durante esta visita:

if(!isset($uid)) {
?>
<!DOCTYPE html PUBLIC "-//W3C/DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <title> Pro favor ingrese sus credenciales </title>
  <meta http-equiv="Content-Type"
   content="text/html; charset=iso-8859-1" />
 </head>
 <body>
  <h1> Login Requerido</h1>
  <p>You must log in to access this area of the site. If you are
   not a registered user, <a href="signup.php">click here</a>
   to sign up for instant access!</p>
  <p><form method="post" action="<?=$_SERVER['PHP_SELF']?>">
   User ID: <input type="text" name="uid" size="8" /><br />
   Password: <input type="password" name="pwd" SIZE="8" /><br />
  <input type="submit" value="Log in" />
  </form></p>
 </body>
</html>
<?php
exit;
}

Cuando se envíe este formulario, se recargará la página, pero esta vez $uid y $pwd  podrán ser seteados con los valores correspondientes. El próximo paso es registrar estas variables como variables sesion, asegurando que estén disponibles mientras dure la visita al sitio.

$_SESSION['uid'] = $uid;
$_SESSION['pwd'] = $pwd;

Igual, hasta el momento, el script no sabe si alguno de los datos es incorrecto, o sea, no sabe si existen dentro de la BBDD. Para averiguarlo, tendremos que encontrar una tupla dentro de la BBDD que corresponda a ambos datos,  la manera fácil (como todo), sería:

dbConnect("sessions"); //nombre de la BBDD
$sql = "SELECT * FROM user WHERE
userid = '$uid' AND password = PASSWORD('$pwd')";
$result = mysql_query($sql);
if (!$result) {
 error('A database error occurred while checking your '.
 'login details.\nIfhis error persists, please '.
 'contact you@example.com.');
}

Con esto chequeamos usando la función mysql_num_rows , y mostramos un mensaje denegando acceso al sitio si no  hubo acierto en la misma. Para hacerlo, deberemos desregistrar las variables sesión parq que la próxima vez que se intente acceder se muestre el formulario de ingreso. Y como las variables fueron registradas antes de este punto, el sistema no tiene que fijarse si existen.

if (mysql_num_rows($result) == 0) {
 unset($_SESSION['uid']);
 unset($_SESSION['pwd']);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <title> Access Denied </title>
  <meta http-equiv="Content-Type"
   content="text/html; charset=iso-8859-1" />
 </head>
 <body>
  <h1> Access Denied </h1>
  <p>Your user ID or password is incorrect, or you are not a
  registered user on this site. To try logging in again, click
  <a href="<?=$_SERVER['PHP_SELF']?>">here</a>. To register for instant
  access, click <a href="signup.php">here</a>.</p>
 </body>
</html>
<?php
exit;
}

Ahora que los detalles han sido resguardados como variables de sesion y validados, ya se puede conceder de manera segura el ingreso al sitio. Lo último a hacer es retornar desde la BBDD el nombre completo del usuario, para poder mostrarlo en pantalla.

$username = mysql_result($result,0,'fullname');
?>

Completando así el archivo  accesscontrol.php, y nuestro sistema de control de acceso. Cualquier página puede usar esto simplemente incluyendo el archivo accesscontrol.php. Por ejemplo:

<?php include 'accesscontrol.php'; ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> Members-Only Page </title>
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1" />
</head>
<body>
<p>Welcome, <?=$username?>! You have entered a members-only area
of the site. Don't you feel special?</p>
</body>
</html>

Cerrando el tema

En este punto ya cerramos el tema, nos faltaría por ejemplo hacer algún formulario para recuperar la contraseña y demás. Pero el control de sesiones se encuentra finalizado.

 

Deja un comentario