Anexsoft | Blog y dictado de cursos de Tecnología

En este encontrarás tutoriales de tecnología como PHP, ASP.NET MVC, Front-End, entre otros y cursos exigentes de modalidad online que te ayudarán a crecer profesionalmente.

Tipos de autenticación: token, session, base de datos con PHP
Actualizado el 17 Julio, 2016 y leído 13,232 veces
Calificación: 10.00 / 10

Tipos de autenticación: token, session, base de datos con PHP

Anexsoft

La siguiente entrada enseñaremos como trabajar con diversas técnicas de autenticación para PHP.

El siguiente POST es con fines educativos ya que hay que mejorar el código presentado y lo mejor y más óptimo es que usen un framework porque ya lo tiene resuelto.

 

Lo más básico

Lo primero que se hace es identificar si el usuario que intenta acceder a nuestro sistema tiene credenciales válidas, es decir si su (nombre de usuario o correo)  y contraseña son iguales a los que estan en la base de datos. Las contraseñas en la base de datos son encriptadas usando algún algoritmo y lo que debemos hacer es encriptar la contraseña para realizar la comparación con la base de datos.

try 
{
    $stm = $this->pdo->prepare(
        "SELECT * FROM usuario WHERE usuario = ? AND password = ?"
    );

    $stm->execute([
        $usuario,
        sha1($password)
    ]);

    $result = $stm->fetch(PDO::FETCH_OBJ);

    if(!is_object($result)) {
        return new Usuario;
    } else {
        return $result;
    }
} catch (Exception $e) 
{
    die($e->getMessage());
}

Nota: en mi caso la contraseña he encriptado usando SHA1, hay buenos algoritmos que podemos usar por ejemplo denle una mirada a esta función de PHP en el siguiente enlace password-hash.

 

Acerca del ejemplo

Eligiendo un proveedor de autenticación

En el ejemplo, en el archivo index.php he creado un script para poder alternar facilmente de proveedor de autenticación, es decir, si queremos trabajar usando una cookie (session), por token (Json Web Token) o por base de datos solo tendríamos que cambiar nuestra constante inicial.

/* Autenticacion: session|db|token */
define('__AUTH__', 'session');

 

Hecho esto, nuestro sistema ya sabría con que forma de autenticación trabajar. Adicionalmente, hay una constante que debemos definir, esta es una SECRET KEY para dar mayor seguridad a nuestro sistema de autenticación, pongan cualquier valor.

define('__SECRET_KEY__', 'asdawdsd8ws.6@');

 

IAuth y Auth

En el proyecto tenemos una interface llamada IAuth, la cual será la encargada de definir el comportamiento a las clases que la extiendan de esta manera me evito de escribir mal por errores los métodos a usar para autenticación y sería mas COOL si usaramos inyección de dependecia, pero no es nuestro caso.

interface IAuth {
    public function autenticar($usuario);
    public function estaAutenticado();
    public function destruir();
    public function usuario();
}

Tambien tenemos 3 clases adicionales que tienen el mismo nombre, cada una de ellas son nuestros proveedor de aunteticación, es decir el mecanismo que vamos a usar para autenticar. El truco esta en que si yo elijo como proveedor usar "session", solo cargará la clase que implementa la aunteticación por session.

 

Por Session (Cookie)

No voy a usar $_SESSION porque no es muy recomendable debido a que traería problemas en servidores compartidos y en otras tecnologías trae problemas de rendimiento porque se guardan directamente en la memoria RAM. Por eso, vamos a usar la COOKIE la cual es guardar en el navegador y enviada al servidor en cada petición que hagamos.

<?php
class Auth implements IAuth {
    private $cookie = '__USUARIO__';
    private $tiempo = 1; // Expresado en horas
    
    public function autenticar($usuario) {
        if(!is_object($usuario)) {
            throw new Exception("Fallo autenticación");
        } else if(empty($usuario->id)){
            throw new Exception("Fallo autenticación");
        }
        
        $extraParaElToken = $usuario->id . $usuario->Usuario;
        
        setcookie(
            $this->cookie,
            json_encode(
                (object) [
                    'id' => $usuario->id,
                    'Usuario' => $usuario->Usuario,
                    'Token' => $this->token($extraParaElToken)
                ]
            ),
            time() + (3600 * $this->tiempo)
        );
    }
    
    public function estaAutenticado() {
        if(!empty($_COOKIE[$this->cookie])) {
            $json = json_decode($_COOKIE[$this->cookie]);
            
            if(empty($json)){
                throw new Exception("No esta autenticado");
            }
            
            if(empty($json->Token)) {
                throw new Exception("No esta autenticado");
            }
            
            $extraParaElToken = $json->id . $json->Usuario;
            
            if($json->Token !== $this->token($extraParaElToken)) {
                throw new Exception("No esta autenticado");
            }
        } else {
            throw new Exception("No esta autenticado");
        }
    }
    
    public function destruir() {
        $this->EstaAutenticado();
        
        unset($_COOKIE[$this->cookie]);
        setcookie($this->cookie, null, -1);
    }
    
    public function usuario() {
        $this->EstaAutenticado();
        
        return json_decode($_COOKIE[$this->cookie]);
    }
    
    private function token($extra){
        return sha1(tokenPorCliente() . $extra);
    }
}
  1. Intentamos autenticar
  2. Pasamos el usuario que respondio verdadero al nombre de usuario y password a nuestra clase Auth
  3. Creamos la cookie y le asignamos un Token único, el cual es una algoritmo que obtiene información única por cliente.

¿La Cookie es segura?

Por naturaleza NO es segura asi que veamos formas de hacerlas más seguras. Entonces lo que he hecho en el ejemplo es crear un valor adicional a la cookie que obtiene una clave única, solo el servidor sabrá como resolverla y sino cumple la condición damos por fallada la aunteticación regresandolo al login de nuestro proyecto.

 

Por base de datos

Para esto se han creado 2 campos en la tabla usuario, una mantiene el token del cliente actual y el otro el tiempo de caducidad (para saber hasta cuando va a ser válido el token).

<?php
class Auth implements IAuth {
    private $pdo;
    private $tiempo = 'PT1H'; // Agrega 1 hora
    
	public function __CONSTRUCT()
	{
		try
		{
			$this->pdo = Database::StartUp();     
		}
		catch(Exception $e)
		{
			die($e->getMessage());
		}
	}
    
    public function autenticar($usuario) {
        if(!is_object($usuario)) {
            throw new Exception("Fallo autenticación");
        } else if(empty($usuario->id)){
            throw new Exception("Fallo autenticación");
        }
        
        $fecha = new DateTime();
        $fecha->add(new DateInterval($this->tiempo));
        
		try 
		{
			$sql = "UPDATE usuario SET 
						Token          = ?, 
						TokenCaducidad = ?
				    WHERE id = ?";

			$this->pdo->prepare($sql)
			     ->execute(
				    array(
                        tokenPorCliente(),
                        $fecha->format('Y-m-d h:i:s'),
                        $usuario->id
					)
				);
		} catch (Exception $e) 
		{
			die($e->getMessage());
		}
    }
    
    public function estaAutenticado() {
        $stm = $this->pdo->prepare(
            "SELECT * FROM usuario WHERE token = ?"
        );

        $stm->execute([tokenPorCliente()]);

        $result = $stm->fetch(PDO::FETCH_OBJ);
        
        if(!is_object($result)) {
            throw new Exception('No esta autenticado');
        }
        
        $token_fecha = new DateTime($result->TokenCaducidad);
        $fecha = new DateTime();
        
        if($token_fecha < $fecha) {
            throw new Exception('No esta autenticado');
        }
    }
    
    public function destruir() {
        $this->EstaAutenticado();
        
        $sql = "UPDATE usuario SET 
                    Token          = null, 
                    TokenCaducidad = null
                WHERE token = ?";

        $this->pdo->prepare($sql)
             ->execute([tokenPorCliente()]);
    }
    
    public function usuario() {
        $this->EstaAutenticado();
        
        $stm = $this->pdo->prepare(
            "SELECT * FROM usuario WHERE token = ?"
        );

        $stm->execute([tokenPorCliente()]);

        return $stm->fetch(PDO::FETCH_OBJ);
    }
}

La lógica es la siguiente:

  1. Verificamos sin nuestras credenciales son correctas
  2. Si fué correcta actualizamos el registro de nuestro usuario modificando el token y el tiempo de caducidad
  3. En cada request habrá una petición a la base de datos para validar el token de cadicidad. ¿Cómo sabes que usuario buscar?, el cliente que navega en nuestra web genera un TOKEN, este token lo busco en mi tabla de usuarios.

 

Por Token (Json Web Token)

Esta forma trabaja con JWT, en al web ya tengo un ejemplo. Para asegurar que el usuario este logeado tenemos que pasar en la URI el siguiente parámetro:

<a href="index.php/home?token=MI_TOKEN_ACTUAL">Nuevo usuario</a>
<form action="index.php/home/actualizar?token=MI_TOKEN_ACTUAL" method="POST">
....
</form>

Debemos pasar a todas nuestras URL, AJAX, lo que sea el TOKEN para que nuestro proveedor de autenticación pueda validarlo.

<?php
use Firebase\JWT\JWT;

class Auth implements IAuth {
    private $encrypt = array('HS256');
    private $tiempo = 1; // Horas
    
    // Crea un nuevo token guardando la información del usuario que hemos autenticado
    public function autenticar($usuario) {
        if(!is_object($usuario)) {
            throw new Exception("Fallo autenticación");
        } else if(empty($usuario->id)){
            throw new Exception("Fallo autenticación");
        }
        
        $time = time();
        
        $token = array(
            'exp'  => $time + (3600*$this->tiempo),
            'aud'  => tokenPorCliente(),
            'data' => $usuario
        );

        return JWT::encode($token, __SECRET_KEY__);
    }
    
    public function estaAutenticado() {
        if(empty($_GET['token'])) {
            throw new Exception('No esta autenticado');
        }
        
        $token = $_GET['token'];
        
        $decode = JWT::decode(
            $token,
            __SECRET_KEY__,
            $this->encrypt
        );
        
        if($decode->aud !== tokenPorCliente()) {
            throw new Exception("No esta autenticado");
        }
    }
    
    public function usuario() {
        $this->estaAutenticado();
        
        $token = $_GET['token'];
        
        return JWT::decode(
            $token,
            __SECRET_KEY__,
            $this->encrypt
        )->data;
    }
    
    public function destruir() {
        
    }
}

 

Adjunto el ejemplo

  1. Descompriman el proyecto
  2. Levanten el backup
  3. El usuario por defecto es erodriguez y el password 123456

¡Adquiera ya!

  • Software de Venta e Inventario hecho en PHP y Codeigniter

    Software de Venta e Inventario hecho en PHP y Codeigniter
  • Software de Portafolio Profesional hecho en ASP.NET MVC 5 C#

    Software de Portafolio Profesional hecho en ASP.NET MVC 5 C#
  • Código de fuente de Red Social desarrollada en ASP.NET MVC

    Código de fuente de Red Social desarrollada en ASP.NET MVC

Últimas publicaciones

Encuesta

¿Cómo nos conociste?

Síguenos

Estudia con nosotros y crece profesionalmente

Nuestros cursos han sido hecho en base a lo que demanda el mercado hoy en día.
La experiencia obtenida es la de un ambiente laboral.

Anexsoft
© 2017 Anexsoft, blog y cursos online de TI.