En este artículo quiero detenerme sobre un punto que, si bien parece simple, tiene sus grises.
Todo el mundo pre-supone que las sesiones son una suerte de baúl mágico donde puede guardarse cualquier cosa y, cual juego de rol, acompaña al héroe a donde vaya.
La realidad no es tan así.
Hay algunos objetos que no pueden ser almacenados en las sesiones PHP.
Un ejemplo que veo con frecuencia es código similar a este:
include("Conexion.php");
$Conexion = Conectar(); --> Donde conectar es una funcion que conecta a mysql
$_SESSION['Conex'] = $Conexion;
Y luego, al querer levantar el dato en otro archivo:
$Conexion = $_SESSION['Conex'];
$Conexion->query('SELECT * FROM user;');
Te encontrás con que la conexión no está en la sesión… ¿qué sucedió?
El problema es que los datos que se guardan en la sesión tienen que ser serializados primero y no todos los objetos son serializables.
De hecho, los objetos de tipo Recurso (Tales como las conexiones a MySQL) no lo son.
Si lo que buscas es evitar abrir y cerrar conexiones a la base de datos en cada script lo que debes hacer es utilizar conexiones persistentes
En este artículo voy a explorar las causas más frecuentes que hacen que las variables de sesión se pierdan y qué podés hacer para solucionarlas.
Te dejo las preguntas que deberías hacerte:
¿Todas las páginas tienen el session_start?
En realidad, no es necesario que todas las páginas lo tengan… de hecho, hasta puede ser contraproducente.
Lo que es seguro es que todas las páginas donde necesites guardar o leer información de la sesión deben invocar esta función.
¿La llamada a session_start está antes de enviar contenido al cliente?
Esta pregunta puede parecer poco sensata. Después de todo, si la llamada a session_start estuviese después de enviar código al cliente verías un error como:
PHP Warning: session_start(): Cannot start session when headers already sent
¿no?
Pues… no necesariamente.
El error se producirá, eso es seguro, pero que lo veas o no en pantalla depende de lo que figure en tu archivo php.ini (Específicamente en la entrada display_errors).
¿El valor de session.use_cookies es 1?
Esta configuración determina que el mecanismo de propagación del ID de sesión sea el uso de una cookie.
Muchas veces ocurre que el valor que tenés en tu entorno local no se condice con el del hosting.
¿El navegador está generando la cookie de sesión?
Esto es fácil de verificar: usualmente esta cookie se llama PHPSESSID (aunque este nombre puede modificarse, también a través del archivo php.ini o la función ini_set).
Si esto no sucede habría que probar si otras cookies se están generando (Lo más probable es que no).
Esto puede deberse a la configuración del navegador (Que no acepta cookies).
La verdad es que en este caso no hay mucho que puedas hacer… más allá de pedir por favor a los visitantes del sitio que habiliten las cookies.
La buena noticia es que la gran mayoría sí lo harán 🙂
¿Cuál es el valor de session.cookie_lifetime?
La configuración session.cookie_lifetime determina cuánto tiempo debe el navegador mantener la cookie de sesión.
Si este tiempo es pequeño la cookie no vivirá lo suficiente como para llegar a la siguiente página.
¿La interacción desde el frontend es muy larga?
Este es un problema que se da mucho en las SPA (Aplicación de una única página) donde casi toda la interacción se da a través de JavaScript.
Si no se realiza ningún pedido al servidor en mucho tiempo se corre el riesgo de que la sesión se considere inactiva y se cierre automáticamente.
En este artículo podés ver un ejemplo de esto (y cómo solucionarlo).
¿Qué valor tiene session.save_path?
Esta configuración determina dónde se almacenan los archivos de sesión (asumiendo, claro, que el mecanismo de almacenamiento elegido sea en archivos).
Si este valor es incorrecto no habrá dónde guardar la información.
Si el valor corresponde a un directorio existente en tu servidor la siguiente pregunta es ¿los archivos de sesión están ahí?
Si no están es probable que haya un problema de permisos: debes validar que el usuario con el que ejecuta tu servidor web sea capaz de escribir allí.
Conclusión
Si estás teniendo problemas para recuperar las variables de sesión es muy probable que se deba a fallas en la configuración de tu entorno, no desesperes: todo tiene solución 🙂
Recorriendo esta lista de preguntas podrás identificar la causa específica de tu problema y resolverlo.
Una fuente de mucha frustración y confusión para quienes arrancan con PHP es el manejo de sesiones.
Hay muchas partes móviles y, a veces, tener todo esto en la cabeza marea…
En este artículo intentaré hacer un repaso por cuáles son esas partes y cómo interactúan entre sí de modo que no te queden dudas respecto de cómo usar esta poderosa herramienta.
Para qué sirven las sesiones
Por supuesto que no puedo avanzar sin antes hablar de lo más importante: ¿para qué sirven las sesiones? O, dicho de otro modo: ¿por qué querrías complicarte la vida entendiendo todo esto?
En pocas palabras: las sesiones sirven para compartir información no entre una página y otra (Un formulario HTML y el php que lo procesa por ejemplo), si no entre todas las páginas por las que el visitante pase.
Un ejemplo simple de esto es cuando visitas un sitio y ves tu nombre de usuario en todas las páginas:
Aunque sólo te identificaste una vez.
Dónde se almacena la información de las sesiones
Una pregunta sumamente importante es dónde se almacena esta información.
Para empezar es importante comprender que la información de la sesión está almacenada del lado del servidor.
Usualmente se guarda físicamente en archivos en el disco de la computadora que actúa como WebServer, aunque esta no es la única opción.
Si querés saber exactamente dónde está guardada la información de las sesiones tenés que consultar dos variables de configuración de php:
En cualquiera de los casos, la información almacenada es el resultado de la serialización de la información.
Cómo se guardan datos en la sesión
Para guardar datos en la sesión de un usuario existe un arreglo llamado $_SESSION.
Este arreglo está presente siempre (es lo que se conoce como una variable super global).
Entonces, guardar un elemento en la sesión del usuario es tan simple como hacer:
<?php
$_SESSION['var'] = 'value';
Cómo se recuperan datos de la sesión
Del mismo modo, recuperar datos de la sesión se reduce a leer el contenido del arreglo:
<?php
$var = $_SESSION['var'];
Cómo se eliminan datos de la sesión
Si lo que necesitas es quitar algún dato de la sesión basta con utilizar la función unset():
<?php
unset($_SESSION['var']);
Cómo se vincula una sesión en el servidor con un cliente
Esperablemente tu aplicación web tendrá muchos usuarios conectados a la vez.
Seguramente no estés muy interesado en que el usuario A vea (o peor… ¡modifique!) los datos de la sesión del usuario B.
Para evitar que esto suceda se necesita que cada sesión pueda vincularse a un (y sólo un) cliente.
Efectivamente, cada sesión tiene un identificador único: el ID de sesión.
En el caso del almacenamiento en archivos, el nombre de cada uno de ellos corresponde al ID de la sesión cuyo contenido está guardado dentro.
Es decir, si tenemos un ID de sesión toqn1cl46210ear4e5t7seg6g7 encontraremos un archivo con ese mismo nombre en el directorio donde se almacenan las sesiones en el servidor.
Si querés saber cuál es tu ID de sesión podés usar la función session_id()
Este ID se genera cuando se crea la sesión, mediante el uso de la función session_start()
Hasta acá no creo que haya mucho misterio, ¿cierto?
Bueno, pues aquí es donde tenemos que ir un poco más atrás para comprender realmente qué pasa.
Las aplicaciones web están basadas en el protocolo HTTP.
Este protocolo estuvo pensado y diseñado en función de las necesidades de la web estática (Es decir, una web en la cual a todos los visitantes se les mostraba el mismo contenido).
En un contexto donde da lo mismo si el que pidió la página B antes había pedido la página A no tiene sentido gastar recursos en implementar sesiones…
Claro que, hoy en día las cosas son MUY diferentes… desafortunadamente, las aplicaciones web siguen estando basadas en HTTP, con lo cual, para mantener la coherencia entre diferentes peticiones sucesivas es necesario recurrir a algunos trucos.
Básicamente, se necesita que el cliente informe al servidor cuál es su ID de sesión en cada petición.
Esto puede realizarse de dos formas:
A través de la URL (agregando un parámetro ?SID=$sid)
Mediante una cookie
El primer método no es muy aconsejable por un tema de seguridad, el segundo, si bien no es infalible, es mucho mejor.
En caso de optar por la segunda opción, necesitarás que esa cookie se genere en el cliente luego de iniciar la sesión.
Podrías usar setcookie, pero sería un poco redundante: session_start() ya lo hace 😉
Claro que, dado que todo este proceso se realiza mediante el intercambio de encabezados HTTP, debes tener cuidado de llamar a session_start()antes de enviar contenido al cliente.
Qué hace exactamente la función session_start()
session_start() es una función algo rara.
Digo rara porque realiza varias tareas según el caso, algo que en general no es muy aconsejable.
Si la petición NO viene con una cookie (o un parámetro SID) O el valor no corresponde con una sesión activa en el servidor:
Se genera un nuevo ID de sesión
Se inicializa el espacio de almacenamiento en el servidor
Se envía el encabezado correspondiente a la creación de la cookie de sesión (Asumiendo por supuesto que este el método de propagación de ID seleccionado)
Si la petición SI viene con una cookie (o un parámetro SID) Y el valor corresponde con una sesión activa en el servidor:
Se lee el contenido del archivo de sesión correspondiente
Se inicializa el arreglo $_SESSION con los valores contenidos en dicho archivo
Cómo se destruye una sesión
Por último, en el caso de que quieras destruir por completo el contenido de una sesión (Por ejemplo, el caso en que un visitante esté abandonando tu aplicación), la función session_destroy() se encargará de ello.
Un detalle importante a tener en cuenta es que session_destroy() sólo actuará del lado del servidor, así que, para mayor seguridad es conveniente usar setcookie() para eliminar la cookie del lado del cliente
En un post anterior había mencionado cuáles son los riesgos a los que están sujetas las sesiones PHP.
En este artículo voy a darte algunos tips para mejorar la seguridad de tus sesiones y dormir más tranquilo 🙂
1. Usa siempre HTTPS
La mejor forma de reforzar la seguridad de tus aplicaciones (No sólo de las sesiones) es utilizar tráfico cifrado, de esta forma, aún si alguien logra capturar el tráfico le será virtualmente imposible leer el contenido.
Puedes conseguir certificados gratuitos usando, por ejemplo, LetsEncrypt
2. Cambia el nombre de tu cookie de sesión
El nombre por defecto de la cookie de sesión es PHPSESSID pero puedes cambiarlo usando session_name() o desde la configuración de php (La variable session.name).
Al cambiar este dato harás más dificultoso para un atacante saber cuál es la cookie que contiene el id de sesión.
3. Habilita el modo de sesión estricto
En este modo (deshabilitado por defecto) el servidor sólo reconocerá como válidas aquellas sesiones cuyo identificador haya sido generado por él.
5. Mantén información adicional dentro de las variables de sesión
Al momento de inicializar una sesión no guardes sólo el id de usuario si no algún otro dato que permita validar la identidad del visitante, por ejemplo:
Seguramente escuchaste alguna versión de estas preguntas:
¿Es posible que alguien modifique sus cookies, les otorgue diferentes privilegios o inicie sesión como un usuario diferente?
¿Puede alguien robar las variables mientras están guardadas en el lado del servidor?
¿Que tan posible es que alguien te copie una session para usarla en su navegador?
Una de nuestras principales preocupaciones como desarrolladores de software es la posibilidad de que algún hacker pueda robar información que los usuarios han confiado a nuestras aplicaciones.
Si eso llegara a suceder nuestros clientes podrían verse en serias dificultades (y por lo tanto, nosotros también).
Existen muchos modos en que un atacante puede colarse en nuestros sistemas y, lamentablemente, las sesiones no están exentas de riesgos.
Para qué sirven las sesiones en PHP
Las sesiones de PHP son un modo bastante cómodo de compartir información entre peticiones.
Un ejemplo clásico es el login: independientemente de cuál sea el método de autenticación usado, una vez validada la identidad del visitante, es necesario indicar a las siguientes páginas que este usuario está autorizado a verlas.
Para ello un modo muy común es utilizar el arreglo $_SESSION con algún código similar a:
<?php
// Obtener el $userId de la base de datos
session_start();
$_SESSION['userId'] = $userId;
Y, en las siguientes páginas tener un chequeo similar a:
<?php
session_start();
if (empty($_SESSION['userId'])) {
header('Location: login.html');
die;
}
Usar las sesiones no es realmente complejo pero si para responder qué tan seguras son las sesiones primero debemos comprender cómo funcionan internamente.
Cómo funcionan las sesiones en PHP
Del lado de nuestros scripts tenemos ese arreglo superglobal ($_SESSION) que, en principio, puede guardar cualquier valor que se desee y estará ahí cuando lo necesitemos.
Existen varias formas de almacenar esta información. La más común es la utilización de archivos temporales en algún lugar del disco del servidor, aunque existen varias alternativas (Puedes consultar más sobre esto en el manual de PHP).
Claro que un mismo servidor web puede estar atendiendo a muchos visitantes a la vez (al menos eso es lo que esperamos que suceda :)), de modo que necesitamos alguna forma de asociar ese archivo en el disco del servidor al visitante que realizó la solicitud:
Múltiples clientes son atendidos en forma simultánea por un servidor web
El protocolo HTTP (En el que se basa la web) pertenece al conjunto conocido como sin-estado (stateless).
Esto significa que cada petición es independiente de las otras… ¡lo cual es precisamente lo contrario de lo que necesitamos!
Debido a esta característica de HTTP, cada aplicación que quiera vincular una petición con otras deberá hacerlo por sus propios medios.
En el caso de PHP la forma de asociar a un visitante con su sesión es agregar un parámetro a cada petición: el ID de sesión.
Hoy en día lo más usual es guardar este id en una cookie especial (La cookie de sesión) y de ese modo asegurarse de que este valor:
Acompañe al usuario en su recorrido por nuestro sitio
Desaparezca en forma automática cuando ya no se lo necesita
Qué riesgos presentan las sesiones de PHP
Como vemos, toda la información de las sesiones está almacenada del lado del servidor, con lo cual, el riesgo de que alguien acceda a dicha información se reduce a dos posibilidades:
Que una persona no autorizada pueda acceder directamente a los archivos del servidor
Que un atacante suplante la identidad de un usuario que ha iniciado sesión
Evitar el primer riesgo no depende de nuestro código si no de la configuración del servidor.
Una forma de atacar una aplicación web a través del mecanismo de sesiones consiste en el secuestro de la sesión (session hi-jacking).
De lo que se trata es de copiar el contenido de una cookie de sesión y crear con él otra cookie en otra computadora.
Si no se han tomado medidas precautorias, el servidor no será capaz de distinguir a un visitante del otro.
Obtener una cookie de sesión no es algo sencillo pero tampoco imposible.
Basta con:
Tener acceso a la computadora de la víctima
Interceptar el tráfico de red (Suponiendo que no esté cifrado)
Muchos clientes se acercan a cualquier desarrollador con la idea de agregar a su sitio un «carrito de compras» pero, cuando indagamos un poco más vemos que el tema no es tan simple.
Por ejemplo: ¿sirve de algo el carrito de compras sin la posisbilidad de realizar el pago al final?
Pero bueno, para no hacer un post enorme, comencemos por la parte del carrito propiamente dicho y dejemos el tema de los pagos para otro.
Qué puede hacerse con un carrito de compras
El carrito de compras es un espacio donde un visitante puede llevar registro de los productos que desea comprar.
Desde el punto de vista técnico/funcional, debe ser posible:
Ingresar productos
Quitar productos
Ver los productos existentes
Confirmar la compra
Para poder ingresar productos al carrito será necesario visualizar cuáles son los productos disponibles en la tienda.
Qué se necesita para armar un carrito de compras
La implementación de todo el mecanismo de administración del carrito requerirá de:
Algún tipo de base de datos que contenga el catálogo de productos
Una aplicación que permita al visitante:
Ver ese catálogo
Ver los detalles de cada producto
Agregar productos a su carrito
Ver los contenidos de su carrito
Eliminar productos del carrito
Confirmar su compra
Una pasarela de pagos para completar la transacción
Por simplicidad imaginemos que tenemos una primera página tipo catalog.php:
Estoy dejando de lado unos cuantos detalles para no complicar el ejemplo: como ser categorías de productos, cantidad a agregar en cada interacción, etc…
Continuemos por el archivo add_to_cart.php.
Aquí es donde reside la clave de la cuestión: el carrito debe acompañar al visitante durante toda su estadía en el sitio.
Precisamente, la idea del carrito es permitir que la compra se vaya armando conforme el usuario va descubriendo los productos.
De modo que necesitaremos un medio de almacenamiento que persista durante la navegación.
A estos efectos php pone a nuestra disposición el arreglo $_SESSION.
Es ahí donde vamos a almacenar los productos seleccionados por el visitante:
De este modo guardamos los ids de los productos seleccionados y su cantidad.
No es necesario guardar más que los ids de producto ya que con eso será suficiente para recuperar toda la información a la hora de mostrar el carrito o confirmar la compra.
Queda por completar los archivos de ver el detalle del producto y remover del carrito pero creo que con lo que viste hasta aquí no deberías tener problemas para armarlos.
Un detalle que haría más amena la experiencia para el usuario sería utilizar AJAX para meter y sacar productos del carrito.
Y, por supuesto, deberás tener un enlace para realizar el pago.
PHP maneja las sesiones a través de cookies (Antiguamente también se podía propagar el ID de sesión vía URL, aunque es una práctica muy poco segura y, sinceramente, hace mucho que no lo veo).
Bien, ahora… ¿qué cosas podrías querer cambiar de la configuración de la sesión? Varias.
El nombre de la cookie
El tiempo de duración
El lugar donde se almacena la información del lado del servidor
Sobre la segunda y la tercera, acá tenés un ejemplo de por qué querrías hacerlo 🙂
Respecto de la primera, más que nada se trata de un tema de seguridad. Fijate esta captura de pantalla de la consola del navegador:
El nombre PHPSESSID es el nombre por defecto que se le asigna a la cookie de sesión de una aplicación hecha en PHP, eso significa que, si te encontrás con una cookie con este nombre es altamente probable (Por no decir seguro) que del otro lado se encuentre una aplicación desarrollada en PHP.
Ese tipo de información es super útil para un atacante, ya que al conocer qué software está corriendo el servidor se le facilita enormemente la tarea de buscar una forma de explotarlo…
Bien, ahora que te convencí de que es una buena idea cambiar el nombre de la cookie, te voy a mostrar cómo hacerlo en Symfony:
Es muy simple, se trata de agregar un par de líneas al archivo config.yml:
Se puede configurar todos los parámetros de la sesión mediante este mecanismo y después, por ejemplo, se puede modificar algunos valores para el entorno de desarrollo definiendo un archivo config_dev.yml con algo como:
En un proyecto que hice para un cliente me sucedió algo que no había previsto: un formulario dinámico resultó muy largo para la persona que tenía que realizar la carga y, cuando terminó el sistema la deslogueó automáticamente y perdió su trabajo 🙁
Analizando un poco el problema me di cuenta de que la sesión había expirado a pesar de que el usuario estaba interactuando con el sistema… sólo que no se estaba produciendo ninguna comunicación cliente-servidor, ya que toda la acción estaba pasando del lado cliente.
A juzgar por algunos comentarios que he leído por ahí, como:
Tengo un archivo sesion.php que se incluye en cada pagina del proyecto, para validar tanto si la sesión esta activa, como si la sesión ha expirado, estoy haciendo pruebas con 30 segundos para no tardar tanto.
El problema surge que el código se ejecuta cada vez que visito una pagina, por lo tanto si estoy navegando sobre la misma pagina, como por ejemplo haciendo click o registrando datos, la sesión aún así expira.
O también:
Tengo mi web en un servidor de GoDaddy y manejo sesiones pero estas caducan solas por inactividad..
Parece que no estoy solo en este sufrimiento 🙂
La solución que encontré fue diseñar un mecanismo de tipo keepAlive de modo de avisar al servidor que todavía había actividad del lado del cliente (¡y pedir por favor que no me dejen afuera!).
Lo primero que hice entonces fue agregar este pequeño código javascript:
La idea es llevar un control sobre cualquier evento que se suceda del lado cliente (Click, presionar una tecla o mover el mouse) y, cada tanto (20 minutos en mi caso), si hubo actividad, avisarle al server para prolongar la vida de la sesión.
Cómo extender dinámicamente la vida de una sesión
Del lado del servidor no hay mucho que hacer en realidad, basta con tener algún script que responda a la URI /keepAlive (Preferentemente con un 200 OK) y listo.
Hay algunas otras cosas a tener en cuenta si, como en mi caso, la aplicación está corriendo en un ambiente de hosting compartido (Algo que trato de evitar por todos los medios, pero bueno… en este caso no pude):
Dónde se almacena la información de las sesiones propias de la aplicación
Cuál es el tiempo de vida de la sesión
Cada cuánto tiempo se libera la basura de las sesiones
La respuesta a estas tres preguntas (y otras más) está en el viejo y querido php.ini. Específicamente en las claves:
Como cualquier otro setting de php, puede modificarse desde el propio código de nuestra aplicación (¡Pero hay que acordarse de hacerlo!).
Un detalle interesante es esta notita sobre session.gc_maxlifetime:
Nota: Si diferentes scripts tienen diferentes valores de session.gc_maxlifetime pero comparten la misma ubicación para almacenar la información de sesión, la información del script con el mínimo valor será limpiada. En este caso use esta directiva junto con session.save_path.
No puedo estar seguro al 100%, pero mi apuesta más fuerte es que este fue el problema: mi aplicación estaba guardando sus archivos de sesión en el mismo lugar que otra y, cuando pasó el garbage collector… ¡adiós!
¿Tuviste alguna vez problemas de este tipo con tus aplicaciones web?