dlunire/dlauth

Biblioteca de autenticación para DLUnire.

Maintainers

Package info

github.com/dlunire/dlauth

Homepage

pkg:composer/dlunire/dlauth

Transparency log

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-08-02 10:26 UTC

This package is auto-updated.

Last update: 2026-08-02 10:32:43 UTC


README

Guía práctica de extremo a extremo para usar DLAuth en un proyecto real, desde el login hasta la revocación de sesiones en todos los dispositivos.

1. Instalación

composer require dlunire/dlauth

2. Filosofía en una frase

DLAuth nunca consulta una base de datos ni lee la petición HTTP por su cuenta. Solo gestiona $_SESSION, decide cuándo toca revalidar contra una fuente externa (mediante un TTL), y delega el cómo por completo al programador. Esto la hace agnóstica de cualquier motor de persistencia (MySQL, PostgreSQL, SQLite, archivo, lo que sea).

3. Crear tu propia clase concreta

DLAuth es abstract — no se instancia directamente. Extiéndela con tu propia clase:

<?php

declare(strict_types=1);

namespace App\Auth;

use DLUnire\DLAuth\Auth\DLAuth;

final class AppAuth extends DLAuth {
    public function __construct(string $field = '__APP_AUTH__') {
        parent::__construct(field: $field);
        $this->set_revalidation_ttl(300); // 5 minutos, opcional (300 es el default)
    }
}

4. Login — crear la sesión (siempre en POST)

Regla de oro: la creación de sesión debe ocurrir exclusivamente en peticiones POST, nunca en GET/HEAD. Mezclarlas abre la puerta a session fixation. La separación la garantiza tu enrutador:

DLRoute::post('/login', [LoginController::class, 'create']); // 201
DLRoute::get('/profile', [ProfileController::class, 'show']);  // Lectura

Dentro del controlador de login:

final class LoginController extends BaseController {
    public function create(): void {
        session_start(); // requerido antes de usar DLAuth

        $auth = new AppAuth();

        // Datos de contexto, opcionales, deben fijarse ANTES de crear la sesión
        // IMPORTANTE: si no usa DLUnire, es posible que tengas que hacer lo que observas
        // aquí abajo:
        $auth->set_ip($_SERVER['REMOTE_ADDR'] ?? '')
             ->set_uri($_SERVER['REQUEST_URI'] ?? '');

        // Si el login incluye 2FA ya resuelto por un proveedor externo
        // (Google/Microsoft Authenticator, OTP por SMS/email):
        // $auth->set_two_factor(true)->set_two_factor_token($otp_result->token);

        $created = $auth->create_session_data([
            'user_id' => $user->id,
            'role' => $user->role,
        ]);

        if (!$created) {
            // Ya existía una sesión válida en este campo — comportamiento
            // "write-once" deliberado de DLAuth. No se sobrescribe.
            http_response_code(409);
            return;
        }

        http_response_code(201);
    }
}

Qué ocurre internamente al llamar create_session_data():

  1. Verifica que haya una sesión de PHP activa (session_start() ya ejecutado); si no, lanza RuntimeException.
  2. Verifica que no exista ya una sesión válida en este campo (write-once).
  3. Solo si va a proceder de verdad, ejecuta session_regenerate_id(true) — mitigación de session fixation: cualquier ID de sesión que el cliente tuviera antes de autenticarse queda invalidado en este preciso momento.
  4. Escribe el registro completo en $_SESSION (datos, timestamp, un token aleatorio de 512 bits, contexto capturado con los set_*()).

5. Leer la sesión — en cada petición (GET/HEAD)

final class ProfileController extends BaseController {
    public function show(): void {
        session_start();

        $auth = new AppAuth();

        $auth->authenticated(function ($session) {
            // $session es un DLUnire\DLAuth\Data\SessionData
            echo "Bienvenido, usuario #{$session->data['user_id']}";
        });

        $auth->unauthenticated(function () {
            http_response_code(401);
        });
    }
}

authenticated()/unauthenticated() evitan que tengas que comprobar is_valid_session por tu cuenta en cada controlador — el callback correspondiente solo se ejecuta si aplica.

6. Verificar un token recibido del cliente

DLAuth nunca lee la petición HTTP directamente. Si necesitas comparar un hash que el cliente envió (por ejemplo, en un header Authorization o una cookie separada del PHPSESSID) contra el token guardado en la sesión, extráelo con DLRoute primero y pásaselo a DLAuth:

$received_hash = $request->get_header('X-Session-Token'); // resuelto por DLRoute

if (!$auth->verify_token($received_hash)) {
    http_response_code(401);
    return;
}

Internamente usa hash_equals(), no === — comparación en tiempo constante, indispensable porque $received_hash proviene de un cliente no confiable.

7. Logout — cerrar sesión en este dispositivo

Puede utilizar HTTP POST o HTTP DELETE.

DLRoute::delete('/logout', [LogoutController::class, 'destroy']);
$auth->logout(); // true si había una sesión y se eliminó

8. Cerrar sesión en todos los dispositivos

Este es el flujo más importante para entender la filosofía de DLAuth. Tú decides de dónde sale el dato de revocaciónDLAuth solo decide cuándo preguntarte, a través del mismo TTL.

use DLUnire\DLAuth\Data\SessionsRemovalRequest;

$auth->delete_all_sessions(function () use ($user_id) {
    // Este callback SOLO se ejecuta si el TTL ya expiró. Mientras la
    // sesión esté dentro del rango configurado, ni siquiera se invoca —
    // cero consultas a tu base de datos en el camino caliente.
    $row = Revoke::where('user_uuid', $user_id)->first();

    return (new SessionsRemovalRequest())
        ->set_key('revoke')       // columna que indica revocación (1/0)
        ->capture_value($row);
});

El contrato es estricto: el callback debe devolver siempre una instancia de SessionsRemovalRequest. Si devuelves cualquier otra cosa, DLAuth lanza TypeError de inmediato, en vez de fallar en silencio más adelante.

Para pruebas, sin tocar ninguna base de datos:

$auth->delete_all_sessions(fn() =>
    (new SessionsRemovalRequest())->set_key('revoke')->capture_value(['revoke' => 1])
);

Flujo completo de decisión dentro de delete_all_sessions():

¿TTL vigente? ──sí──► no se invoca el callback, no se elimina nada
     │no
     ▼
invoca el callback → exige SessionsRemovalRequest
     │
     ▼
¿is_removable() === true? ──no──► no se elimina nada
     │sí
     ▼
delete_session_data()

9. Cookies — Cookie y SameSite

Si necesitas transportar el token de sesión (o cualquier otro valor) en una cookie separada de PHPSESSID, usa Cookie:

use DLUnire\DLAuth\Context\Cookie;
use DLUnire\DLAuth\Enums\SameSite;

(new Cookie('session_token', $session->token))
    ->set_secure($is_https)      // resuelto por DLRoute, no por Cookie
    ->set_same_site(SameSite::STRICT)
    ->set_expires(time() + 3600)
    ->dispatch();

Defaults ya orientados a seguridad: http_only = true siempre, y same_site = SameSite::LAX si no se configura explícitamente. secure queda en false por defecto de forma deliberada — Cookie no tiene visibilidad propia del esquema HTTP/HTTPS de la petición actual; esa determinación le corresponde a DLRoute.

Consultar o eliminar una cookie existente, sin instanciar nada:

Cookie::has('session_token');       // bool
Cookie::get('session_token');       // ?string
Cookie::remove('session_token');    // usa los mismos path/domain originales

10. Resumen de responsabilidades

Pieza Responsabilidad
Auth Bajo nivel: TTL, lectura/escritura de $_SESSION, puntos de extensión abstractos.
DLAuth Orquestación de alto nivel: authenticated(), logout(), verify_token(), create_session_data() con regeneración de ID.
SessionData DTO inmutable que representa el registro de sesión.
SessionsRemovalRequest DTO configurable que decide si una sesión debe eliminarse, a partir de un array que resuelves.
Cookie / SameSite Transporte de valores en cookies HTTP, con defaults seguros.
DLRoute Enrutamiento, separación POST/GET, resolución del esquema HTTP/HTTPS, extracción de headers.

DLAuth nunca cruza estas fronteras: no consulta bases de datos, no lee la petición HTTP directamente, no decide el esquema de cifrado del transporte. Cada pieza hace una sola cosa.

11. Consideraciones de seguridad ya cubiertas

  • Entropía del token: hash('sha384', random_bytes(64)) — 512 bits de una CSPRNG.
  • Comparación en tiempo constante: verify_token() usa hash_equals().
  • Mitigación de session fixation: session_regenerate_id(true) en cada creación de sesión exitosa.
  • Write-once: una sesión ya creada no puede sobrescribirse por accidente.
  • Defaults seguros en cookies: http_only = true por defecto.
  • Separación POST/GET: responsabilidad de DLRoute, refuerza la mitigación de session fixation a nivel de arquitectura.

12. Fuera del alcance de DLAuth (necesario en otras capas)

  • CSRF en el formulario que dispara el POST de login/logout.
  • Rate limiting sobre el endpoint de login, para mitigar fuerza bruta.
  • Comparación activa de ip/user_agent contra la petición actual para detectar reuso de token desde un contexto distinto — SessionData guarda estos valores, pero no los compara por sí sola.

Estas piezas deben resolverse en DLRoute o en middlewares de la aplicación consumidora, no dentro de DLAuth.