2/21/2010

Como realizar un sistema de Login en PHP y MySQL

En el siguiente artículo mostraremos como realizar un sistema de Login o Autenticación de usuarios con la ayuda de PHP y Mysql.

Introducción a la autenticación

La autenticación nos permite identificar a un usuario, cuando este presente su nombre de usuario y contraseña. En general estos datos son guardados por el servidor en una base de datos como MySQL y se utilizan para validar los datos ingresados en el momento del Login.

Una vez realizado el Login exitoso, se registra el login en la sesión. Esto nos permite asegurar páginas simplemente pidiendo que exista la sesión del usuario logueado.

La acción de Logout (o Cerrar sesión), se refiere a destruir esta información de la sesión, así el usuario dejará de ser un usuario autenticado.

La siguiente imagen muestra un diagrama dónde se representa el flujo antes explicado

Diagrama de flujo para lograr autenticación

Base de datos

Empecemos por crear la tabla necesaria para lograr la autenticación, requerimos minimamente almacenar un nombre de usuario y una contraseña, por lo que nuestra tabla de usuarios llevará las columnas: username y password.

Nos conectamos a mysql o a phpmyadmin y creamos nuestra tabla de usuarios:

test@test-laptop:~/$ mysql -uroot -p
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 45
Server version: 5.1.37-1ubuntu5.1-log (Ubuntu)

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database ejemplo;
Query OK, 1 row affected (0.00 sec)

mysql> use ejemplo;
Database changed
mysql> create table usuarios (id int not null auto_increment primary key, username varchar(100), password varchar(40)) type=innodb;
Query OK, 0 rows affected, 1 warning (0.14 sec)

mysql> alter table usuarios add unique (username);
Query OK, 0 rows affected (0.27 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> describe usuarios;
+----------+--------------+------+-----+---------+----------------+
| Field    | Type         | Null | Key | Default | Extra          |
+----------+--------------+------+-----+---------+----------------+
| id       | int(11)      | NO   | PRI | NULL    | auto_increment | 
| username | varchar(100) | YES  | UNI | NULL    |                | 
| password | varchar(40)  | YES  |     | NULL    |                | 
+----------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

En la línea 13, es dónde creamos la tabla. Incorporamos una primary key `id`, y en la línea 16 también agregamos un índice `unique` para asegurarnos que los usuarios no tengan el `username` duplicado.

Por cuestiones de seguridad no guardaremos los passwords en texto plano, por eso elegimos para la columna password un tamaño de 40 caracteres, ya que este es el ancho de cualquier cadena hasheada por la función SHA1.

Clase UserAutentication

Pasemos ahora a escribir la clase de autenticación de usuario que se encargará de la mayoría del trabajo de nuestro sistema de Login. Las responsabilidades de esta clase serán:

  • Responder si el usuario actual está en sesión y autenticado.
  • Autenticar a un usuario contra la base de datos y guardar la autenticación en sesión con un username y password.
  • Desautenticar (logout) a un usuario que se encuentra en sesión.

Definidas estas responsabilidades podemos ahora dar tres métodos públicos para cada una de estas:

  • bool isAuthenticated()
  • bool doLogin($username,$password)
  • doLogout()

La clase usará un objeto PDO proporcionado en el constructor por el cliente del objeto, o bien intentará crear uno con las constantes de clases predefinidas.

Definido esto pasamos a la implementación propiamente dicha de la clase:

<?php
/*
 * UserAuthentication:
 *
 */


class UserAuthentication
{

  private $_session_namespace = "sistema_usuario"; /* Nombre de
                                                      espacio para no
                                                      ensuciar en
                                                      cualquier lugar
                                                      el super arreglo
                                                      de $_SESSION */

  private $_user_table = "usuarios"; /* Nombre de la tabla de usuarios */

  private $_db;

  /* Constantes de clase para crear una conexión por defecto */
  const default_db_user = 'root';
  const default_db_pass = 'root';
  const default_db_name = 'ejemplo';
  const default_db_host = 'localhost';

  /*
   * Debe recibir una conexión PDO válida o intentará crear una.
   */
  public function __construct($pdoConnection = NULL)
  {
    if(!$pdoConnection)
      $pdoConnection = $this->getConnection();
    $this->_db = $pdoConnection;
  }

  /*
   * Crea una conexión PDO con los datos por default definidos como
   * constantes de clase.
   */
  private function getConnection()
  {
    $dsn = 'mysql:dbname='.self::default_db_name.';host='.self::default_db_host;
    try {
      $dbh = new PDO($dsn, self::default_db_user, self::default_db_pass);
    } catch (PDOException $e) {
      die( 'Connection failed: ' . $e->getMessage() );
    }
    return $dbh;
  }
  
  /*
   * Responde si el usuario activo está autenticado en sesión.
   */
  public function isAuthenticated()
  {
    return $this->getSession('user_authenticated') === true;
  }
  /*
   * Realiza un Login y guarda los datos en sesión.
   */
  public function doLogin($username,$password)
  {
    $result = $this->checkLoginInDB($username,$password);
    if(!$result) {
      $this->doLogout();
      return false;
    }

    $this->setSession('user_authenticated',true);
    $this->setSession('username',$result['username']);
    $this->setSession('id',$result['id']);

    return true;
  }

  /*
   * Quita al usuario de sesión.
   */
  public function doLogout()
  {
    $this->destroySession();
  }

  /*
   * Verifica si el usuario está en la base de datos.
   */
  private function checkLoginInDB($username,$password)
  {
    try {
      $query = 'SELECT id, username FROM '. $this->_user_table . ' WHERE ';
      $query .= ' username = ? AND password = ? ';
      $sth = $this->_db->prepare($query);
      $sth->execute(array($username,$this->encryptPassword($password)));
      return $sth->fetch(PDO::FETCH_ASSOC);
    } catch (PDOException $e) {
      die( 'Query failed: ' . $e->getMessage() );
    }
  }

  /*
   * Encripta un password en texto plano.
   */
  private function encryptPassword($password)
  {
    return sha1($password);
  }
  
  private function getSession($key)
  {
    $session = $_SESSION[$this->_session_namespace];
    if(isset($session[$key]))
      return $session[$key];
    return null;
  }
  private function setSession($key,$val)
  {
    return $_SESSION[$this->_session_namespace][$key] = $val;
  }
  private function destroySession()
  {
    $_SESSION[$this->_session_namespace] = null;
    unset($_SESSION[$this->_session_namespace]);
  }

}

Asegurando páginas

Ahora sólo nos queda asegurar páginas valiendonos de la clase UserAuthentication. Para esto vamos a guiarnos por el diagrama de flujo antes mostrado.

Entonces suponiendo que queremos asegurar una página galeria.php usamos el siguiente código:

<?php
require_once('UserAuthentication.class.php');  
// utilizamos conexion por defecto.
$userAuthentication = new UserAuthentication();

if(!$userAuthentication->isAuthenticated()) {
  // el usuario no está autenticado requiere login.
  header( "Location: /login/login.php?uri=". base64_encode($_SERVER["REQUEST_URI"]));
}
// contenido seguro
?>
<html>
  <head>
  <title>Pagina segura</title>
  </head>
  <body>Contenido seguro
  <a href="http://localhost/login/logout.php">Cerrar sesion</a>
  </body>
</html>

El archivo login.php es el formulario que se encargará de autenticar, también valiéndose de la clase UserAuthentication:

<?php
require_once('UserAuthentication.class.php');  
if(isset($_POST['username']) && $_POST['password']) {
  $username = $_POST['username'];
  $password = $_POST['password'];
  $uri = isset($_POST['uri']) && !empty($_POST['uri']) ? base64_decode($_POST['uri']) : '/';
  $userAuthentication = new UserAuthentication();
  if($userAuthentication->doLogin($username,$password)) {
    header("Location: ". $uri);
  } else {
    $msg_error = 'Usuario o password invalidos';
  }
}
?><html>
  <head>
  <title>Login</title>
  </head>
  <body>
    <?php if(isset($msg_error)): ?>
       <p style="color: red;"><?php echo $msg_error ?></p>
    <?php endif; ?>
    <form method="post" action="login.php">
      Usuario: <input type="text" name="username" /><br />
      Password: <input type="password" name="password" />
      <input name="uri" type="hidden" value="<?php echo isset($_GET['uri']) ? $_GET['uri'] : ''  ?>" /><br />
      <input type="submit" />
    </form>
  </body>
</html>

Por último el script logout.php se encargará de de destruir la sessión.

<?php
require_once('UserAuthentication.class.php');  
$userAuthentication = new UserAuthentication();
$userAuthentication->doLogout();
?>
<html>
  <head>
    <title>Logout</title>
  </head>
    <body>
      Sesion cerrada
    </body>
</html>

24 comentarios:

Anónimo dijo...

private function checkLoginInDB($username,$password)
{
try {
$query = 'SELECT id, username FROM '. $this->_user_table . ' WHERE ';
$query .= ' username = ? AND password = ? ';
$sth = $this->_db->prepare($query);
$sth->execute(array($username,$this->encryptPassword($password)));
return $sth->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die( 'Query failed: ' . $e->getMessage() );
}
}

Buenas, no entiendo que hacen esas interrogaciones en la query, me lo podrias explicar?

Anónimo dijo...

Hola,
¡Muchas gracias por tu ejemplo!
pero hay algo que se me escapa: me puedo loguear bien, pero cuando pongo el código correspondiente en la "página segura", el sistema no se acuerda de mí, y siempre me dice que no estoy logueado.
No entiendo mucho de PHP, pero parece que estoy creando una nueva instancia de UserAuthenticacion que no contiene la informaciónde login.
¿Me puedes echar un cable?
Gracias,
Pablo L.

Anónimo dijo...

Hola otra vez,
Ya veo lo que pasa, una vez logueado correctamente hago una redirección a la página de bienvenida con un "header", y es en este momento cuando pierdo las variables de $_SESSION. En todos los scripts tengo puesto el session_start(). ¿Cómo es que pierdo las variables?
Gracias,
Pabol L.

Diego dijo...

Anónimo lo que pasa es que debes editar el archivo php.ini buscar donde dice:

session.auto_start = 0

y cambiarlo por:

session.auto_start

y asunto arreglado.

Diego dijo...

Perdón quise decir:

session.auto_start = 0

y cambiarlo por:

session.auto_start = 1

(cambiar el cero por uno)

Anónimo dijo...

¿Como se inserta un usuario en la base de datos? ¿Debe encriptarse?

Gracias de antemano.

Anónimo dijo...

Hola:

Como puedo obtener el valor del "username" ??

Por ejemplo para poner : Bienvenido "usuario" ....

No consigo sacarlo :S Gracias y muy bueno el sistema de login. Me funciona perfecto.

Anónimo dijo...

con getSession('username');

Anónimo dijo...

Perfecto ! muchas gracias voy a probarlo ahora mismo, yo estaba probando con $_SESSION['username'] y demas posibilidades :D

gracias !

Anónimo dijo...

Pues sigue sin funcionar, tengo esta código de ejemplo para que veais...

http://pastebin.com/qz6JQbkh

No consiguo sacar con un "echo" el nombre del usuario que ha metido en el formulario html a través de un INPUT normal ....

Anónimo dijo...

Así lo tenes que usar:
$userAuthentication->getSession('username');

Anónimo dijo...

Pero hay qu eponer un echo $userAuthentication->getSession('username'); o algo ?

No he conseguido aún que me salga :S

Anónimo dijo...

A ver si puedes echarme una mano porque he intentado de todo ya .... ando mirando otros sistemas de logins, pero este me funciona perfecto excepto este pequeño detalle que no logro sacar.

Anónimo dijo...

Claro tiene que poner el echo.

Es decir por debajo de dónde dice:

//contenido seguro

Prueba poner:

echo $userAuthentication->getSession('username');

Eso te tiene que sacar el username.

Anónimo dijo...

Hola:

El nombre de mi table de "username" se llama "email" por otros requerimientos, es lo único diferente y lo único que he cambiado del sistema de Login aquí explicado.

echo $userAuthentication->getSession('email');

Aquí el código:
http://pastebin.com/r7WRNJxg

Solo cambio "username" por "email", pero sigue sin funcionar... ¿es posible que sea esto por lo que no funcione ?? Dudo bastante, pero bueno ....

Gracias por responder

Anónimo dijo...

Buscaste todas las ocurrencias de `username` y las cambiaste por `email` ?
Por ejemplo:


$this->setSession('username',$result['username']);

Debería quedar:


$this->setSession('email',$result['email']);

Y así en todos lados. No pueod verificar que lo hayas hecho porque no me pasaste todos los archivos te faltó UserAuthentication.class.php

Anónimo dijo...

Hola:

Si, absolutamente todas.....

Te he pegado aqui el login, prueba.php y el userautenthicacion...

http://pastebin.com/rsZbHq3x

Anónimo dijo...

Hola de nuevo: imposible hacer funcionar el "echo" ese eh .... me doy por vencido :S

Anónimo dijo...

Buen día, tu publicación me ha servido de mucho sin embargo me pasa algo extraño, al momento de hacer el login me manda a localhost...por qué sucede esto? Intenté que fuera a otra página pero no lo hace.

Abraham Rosales dijo...

Hola amigo, disculpa copie exactamente lo que tienes en tu ejemplo pero me muestra un error en rojo USUARIO O PASSWORD invalidos, y he creado un usuario y password en la tabla, y no me permite usarlo, no sabes si eso tenga que ver con la configuración de php o algo así por el estilo. Podes ayudarme

Abraham Rosales dijo...

Hola ya logre solucionar mi inconveniente, creo que se estaba encriptando la password, bueno ahora un favor, a mi también me manda al localhost de xampp que hago para que me diriga a otra pagina

Abraham Rosales dijo...

nice ya me funciono, para los que los redirige al LOCALHOST solo deben agregar en la pagina de login.php debajo del segundo if, esto header( "Location: /login/galeria.php");

y comitar todo lo que diga $uri pq ese fue el calvo que me dio a mi

archibinario dijo...

Si quieres obtener el nombre del usuario prueba a crear un método nuevo en UserAuthentication.class.php
public function getUsername(){
return $this->getSession('username');
}

y llámalo desde galeria.php

getUsername(); ?>

ä

Anónimo dijo...

hola a todos, aunq se ke ya tiene tiempo, me gustaria agregar para aquellos a los ke les dio el mismo problema q a mi, sobre que la sesion no permanecia. l solucion consiste en agregar session_start(); en cada uno de los metodos publicos para que retome la sesion.