Lección 17: Login

Dentro de nuestros sitios puede ser que necesitemos disponer de una página separada que solo sea accesible por medio de una contraseña. Un lugar donde solo el usuario pueda entrar y muestre una información sensible:

  • Configuración.
  • Perfil.
  • Datos bancarios.
  • Conversaciones de chat.
  • Historial.

Cualquier elemento que sea único y privado para el visitante.

La estrategia que siempre se ha usado desde el Back-End es por medio de sesiones. Al introducir un usuario/contraseña se creará una llave única que dejará entrar a nuestro visitante, y ¡solo a él!. Si quisiera irse dispondrá de un botón para romper la sesión haciendo imposible que nadie pudiera entrar con el equipo. A no ser que vuelva a indentificarse, claro; no es bueno condenar el usuario al olvido permanente.

Para el ejemplo necesitaremos 3 páginas: login.php (donde nos identificaremos), privado.php (donde entraremos) y cerrar-sesion.php (código que destruirá el acceso).

Cifrando nuestra contraseña

Hablamos del formulario donde podamos dar nuestro nombre de usuario y contraseña. En el ejemplo usaré un email en lugar del nombre.

Si los datos son incorrectos mostrará un mensaje de aviso.

Nunca debe desvelarse si el usuario se ha equivocado en el email o en la contraseña. Revelarías a un posible atacante que uno de los 2 datos son correctos. En su lugar dar una información más genérica: “El email o contraseña es inválido”, “Sus datos de acceso no son correctos”, etc…

Las contraseñas, por razones de seguridad, deben estar encriptadas en la base de datos. Para ello uno de los métodos recomendables es el siguiente.

// La contraseña es '123'
echo password_hash('123', PASSWORD_BCRYPT);
// $2y$10$OuIiaiZMrVb5nAzrBU4U8eBjCB/rMPEAnNlmM8krh0nZ5Fru/nO7q

Si ejecutas el código comprobarás para tu sorpresa que es diferente del mío. Incluso si lo vuelves a ejecutar… será ¡diferente al anterior!

Login

El resultado varía en cada ocasión. ¿Cómo diablos se puede comprobar un sistema tan aparentemente aleatorio? Con la magía de la criptográfia.

password_verify('123', '$2y$10$OuIiaiZMrVb5nAzrBU4U8eBjCB/rMPEAnNlmM8krh0nZ5Fru/nO7q')
// True

Ahora veamos todo unido y orquestado. Si quieres repasar las sesiones puedes volver al capítulo 11 Sesiones.

Llamaremos el archivo login.php.

<html>
    <body>
        <?php 
            // Comprobamos que nos llega los datos del formulario
            if ($_SERVER['REQUEST_METHOD'] == 'POST') {

                // Base de datos ficticia que se usará en el ejemplo.
                $baseDeDatos = [
                    'email' => 'correo@falso.com',
                    'password' => password_hash('123', PASSWORD_BCRYPT)
                ];
                
                // Variables del formulario
                $emailFormulario = isset($_REQUEST['email']) ? $_REQUEST['email'] : null;
                $contrasenyaFormulario = isset($_REQUEST['contrasenya']) ? $_REQUEST['contrasenya'] : null;

                // Comprobamos si los datos son correctos
                if ($baseDeDatos['email'] == $emailFormulario && password_verify($contrasenyaFormulario, $baseDeDatos['password'])) {
                    // Si son correctos, creamos la sesión
                    session_start();
                    $_SESSION['email'] = $_REQUEST['email'];
                    // Redireccionamos a la página segura
                    header('Location: privado.php');
                    die();
                } else {
                    // Si no son correctos, informamos al usuario
                    echo '<p style="color: red">El email o la contraseña es incorrecta.</p>';
                }
            }
        ?>
        <form method="post">
            <p>
                <input type="text" name="email" placeholder="Email"> 
            </p> 
            <p>
                <input type="password" name="contrasenya" placeholder="Contraseña"> 
            </p>
            <p>
                <input type="submit" value="Entrar"> 
            </p>
        </form>
    </body>
</html>

Zona privada

Comprobamos si existe la sesión, en caso contrario devolvemos a login.php de forma automática.

Llamaremos el archivo privado.php.

<?php
// Activa las sesiones
session_start();
// Comprueba si existe la sesión 'email', en caso contrario vuelve a la página de login
if (!isset($_SESSION['email'])) header('Location: login.php');
?>
<html>
    <body>
        <p>
            ¡Te encuentras en una zona secreta!, solo visible por una persona identificada.
        </p>
        <p>
            <a href="cerrar-sesion.php">Cerrar sesión</a>
        </p>
    </body>
</html>

Cerrar sesión

Nada nuevo. Destruimos las sesiones y redirecionamos a login.php.

Llamaremos el archivo cerrar-sesion.php.

<?php
// Inicia las sesiones
session_start();
// Destruye cualquier sesión del usuario
session_destroy();
// Redirecciona a login.php
header('Location: login.php');

Recuperar contraseña

Lo peor que puede ocurrir es que un usuario pierda una contraseña, tanto para él como para programador que ha montado el sitio. Para poder recuperarla tendremos que seguir unos pasos bastantes estudiados.

  1. Comprobar que el usuario es legítimo y no un oportunista mediante un enlace seguro.
  2. Al estar las contraseñas cifradas, como hemos visto antes, no será posible enseñarla. No quedará otra que pedir una nueva contraseña.
  3. Sobrescribir la contraseña anterior con la nueva.
  4. Regañar al usuario. Puede ser sutilmente con un texto donde se le sugiere a guardarla en un sitio seguro.
  5. Llevar a la página de identificacióni (login.php).

La estrategia puede ser por medio de un sistema de verificación fiable. Como puede ser un SMS, un email, una notificación, un mensaje por medio de un chat… de aquí la importancia de dar unos datos reales.

¿Cómo puedo crear un enlace seguro?

Enviaremos un enlace por medio de un email con un número aleatorio el cual nosotros guardaremos y nos servirá para averiguar si el usuario es el mismo que nos esta reclamando la nueva contraseña. En caso contrario lo ignoramos.

Por ejemplo un enlace con la siguiente estructura:

recuperar_contrasenya.php?verificar=123456789

Guardaré el valor de verificar para más adelante comprar.

Una forma segura de generar el número es por medio de openssl.

echo bin2hex(openssl_random_pseudo_bytes(16));
// 60727b7a5f11688aad3662bbd62e065a

Siempre que lo ejecutemos nos proporcionará un string alfanumérico fantásticamente difícil de predecir.

Nuestro código quedaría tal que así.

$tokenSeguro = bin2hex(openssl_random_pseudo_bytes(16));

// Nuestro mensaje en HTML
$mensaje = "
<html>
    <head>
        <title>Recupera la contraseña</title>
    </head>
    <body>
        <a href=\"ejemplo.com?token=$tokenSeguro\">Pulsa aquí para cambiarla</a>
    </body>
</html>
";

// Define que será de tipo será nuestro mensaje: HTML. Y la dirección del emisor.
$headers = [
    'MIME-Version' => '1.0',
    'Content-type' => 'text/html; charset=utf-8',
    'From' => 'curso@php.com'
];

// Lo enviamos
mail('correo@falso.com', 'Recuperar contraseña', $mensaje, $headers);

Ya solo necesitaremos comprobar que el token que hemos guardado es el mismo y continuamos los pasos anteriores.

18-1 18-2 18-3