nicolas2-dev/npds-cookie

Complete cookie management library with Cookie and CookieJar classes

Installs: 1

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/nicolas2-dev/npds-cookie

2.0.0 2025-12-19 18:08 UTC

This package is auto-updated.

Last update: 2025-12-19 18:09:14 UTC


README

Une bibliothèque PHP complète pour la gestion des cookies HTTP avec une API orientée objet, respectant les standards de sécurité modernes.

Installation

composer require npds/cookie

Table des matières

Introduction

Cette bibliothèque fournit une interface orientée objet pour gérer les cookies HTTP en PHP. Elle supporte toutes les fonctionnalités modernes des cookies, y compris SameSite, HttpOnly, Secure, et offre une gestion centralisée via une collection de cookies (CookieJar).

Classes principales

Npds\Cookie\Cookie

Représente un cookie HTTP individuel avec toutes ses propriétés.

Npds\Cookie\CookieJar

Gère une collection de cookies avec des valeurs par défaut et facilite l'envoi des cookies au navigateur.

La classe Cookie

Création d'un cookie simple

use Npds\Cookie\Cookie;

// Cookie de session (expire à la fin de la session)
$cookie = new Cookie('nom_du_cookie', 'valeur_du_cookie');

// Cookie avec expiration en secondes
$cookie = new Cookie('remember_me', 'yes', 3600); // Expire dans 1 heure

// Cookie avec tous les paramètres
$cookie = new Cookie(
    'session_id',
    'abc123def456',
    3600,                    // Expire dans 1 heure
    '/admin',                // Chemin
    'example.com',           // Domaine
    true,                    // Secure (HTTPS uniquement)
    true,                    // HttpOnly (inaccessible en JavaScript)
    'Strict',                // SameSite
    false                    // Raw (non-encodé)
);

Méthodes de fabrication

use Npds\Cookie\Cookie;

// Cookie de session (expire à la fin de la session)
$sessionCookie = Cookie::createSessionCookie('session', '12345');

// Cookie qui expire dans un délai spécifique
$expiringCookie = Cookie::createExpiringCookie('remember', 'yes', 7200); // 2 heures

// Cookie qui expire à une date précise
$date = new DateTime('+1 day');
$dateCookie = Cookie::createFromDate('newsletter', 'subscribed', $date);

Getters et vérifications

$cookie = new Cookie('test', 'value', 3600);

// Récupérer les propriétés
$name = $cookie->getName();        // 'test'
$value = $cookie->getValue();      // 'value'
$expires = $cookie->getExpires();  // Timestamp
$path = $cookie->getPath();        // '/'
$domain = $cookie->getDomain();    // null
$isSecure = $cookie->isSecure();   // false
$isHttpOnly = $cookie->isHttpOnly(); // true
$sameSite = $cookie->getSameSite(); // 'Lax'

// Vérifications
$isExpired = $cookie->isExpired(); // false si pas encore expiré
$isRaw = $cookie->isRaw();         // false

// Vérifier si le cookie correspond à une requête
$matches = $cookie->matches('/admin', 'example.com', true);

Méthodes d'immutabilité

$original = new Cookie('test', 'old', 3600);

// Créer une copie avec une nouvelle valeur
$newCookie = $original->withValue('new');

// Créer une copie avec une nouvelle date d'expiration
$newExpiry = $original->withExpires(time() + 7200);

// Créer une copie avec un nouveau chemin
$newPath = $original->withPath('/api');

// Créer une copie avec un nouveau domaine
$newDomain = $original->withDomain('api.example.com');

// Créer une copie avec Secure activé
$newSecure = $original->withSecure(true);

// Créer une copie avec HttpOnly désactivé
$newHttpOnly = $original->withHttpOnly(false);

// Créer une copie avec une nouvelle politique SameSite
$newSameSite = $original->withSameSite('None'); // Note: nécessite Secure=true

Validation et exceptions

use Npds\Cookie\Cookie;
use InvalidArgumentException;

try {
    // Nom invalide (contient des espaces)
    $cookie = new Cookie('nom invalide', 'value');
} catch (InvalidArgumentException $e) {
    echo $e->getMessage();
    // Le nom du cookie "nom invalide" contient des caractères invalides.
}

try {
    // SameSite=None sans Secure
    $cookie = new Cookie('test', 'value', 3600, null, null, false, null, 'None');
} catch (InvalidArgumentException $e) {
    echo $e->getMessage();
    // Les cookies avec SameSite=None doivent être définis avec secure=true
}

La classe CookieJar

Initialisation

use Npds\Cookie\CookieJar;

// Avec les valeurs par défaut
$jar = new CookieJar();

// Avec des valeurs par défaut personnalisées
$jar = new CookieJar(
    '/api',              // Chemin par défaut
    'api.example.com',   // Domaine par défaut
    true,                // Secure par défaut
    false,               // HttpOnly par défaut
    'None'               // SameSite par défaut
);

// Modifier les valeurs par défaut après création
$jar->setDefaultPath('/admin')
    ->setDefaultDomain('admin.example.com')
    ->setDefaultSecure(true)
    ->setDefaultHttpOnly(true)
    ->setDefaultSameSite('Strict');

Ajouter des cookies

$jar = new CookieJar();

// Méthode générique
$jar->set('user_id', '12345', 3600);

// Cookie de session
$jar->setSessionCookie('session_token', 'abc123def456');

// Cookie qui expire dans un délai
$jar->setExpiringCookie('remember_me', 'yes', 86400); // 24 heures

// Avec tous les paramètres (écrase les valeurs par défaut)
$jar->set(
    'preferences',
    'dark_mode:true',
    31536000,           // 1 an
    '/settings',        // Chemin spécifique
    'app.example.com',  // Domaine spécifique
    true,               // Secure
    true,               // HttpOnly
    'Lax',              // SameSite
    false               // Raw
);

Récupérer et vérifier les cookies

$jar = new CookieJar();
$jar->set('test', 'value');

// Récupérer un cookie
$cookie = $jar->get('test');
if ($cookie) {
    echo $cookie->getValue(); // 'value'
}

// Vérifier l'existence
$hasCookie = $jar->has('test'); // true

// Récupérer tous les cookies
$allCookies = $jar->all(); // array<string, Cookie>

// Compter les cookies
$count = $jar->count(); // 1

// Vérifier si le jar est vide
$isEmpty = $jar->isEmpty(); // false

Supprimer des cookies

$jar = new CookieJar();
$jar->set('test', 'value', time() + 3600);

// Supprimer un cookie (le marque comme expiré)
$jar->remove('test');

// Vérifier que le cookie est maintenant expiré
$cookie = $jar->get('test');
if ($cookie && $cookie->isExpired()) {
    echo 'Cookie expiré avec succès';
}

// Vider tous les cookies
$jar->clear();

Filtrer et rechercher

$jar = new CookieJar();
$jar->set('valid', 'value1', time() + 3600);
$jar->set('expired', 'value2', time() - 3600);

// Filtrer les cookies expirés
$validCookies = $jar->filterExpired();
echo $validCookies->count(); // 1

// Trouver les cookies correspondant à une requête
$jar = new CookieJar('/admin', 'example.com');
$jar->set('admin_session', 'abc123');
$jar->set('user_prefs', 'theme:dark');

$matched = $jar->match('/admin/dashboard', 'example.com', true);
// Retourne: ['admin_session' => 'abc123', 'user_prefs' => 'theme:dark']

Gestion des en-têtes HTTP

$jar = new CookieJar();

// Ajouter des cookies
$jar->set('session', 'abc123', time() + 3600);
$jar->set('preferences', 'theme:dark', time() + 86400);

// Envoyer tous les cookies au navigateur
$jar->send();

// Les cookies sont maintenant envoyés via les en-têtes Set-Cookie

// Charger des cookies depuis la requête HTTP
$requestCookies = CookieJar::fromRequest();
// $requestCookies contient un tableau associatif des cookies reçus

// Charger des cookies dans le jar
$jar->load([
    'existing_cookie' => 'value',
    'another_cookie' => 'another_value'
]);

Exemples complets

Exemple 1 : Gestion d'une session utilisateur

use Npds\Cookie\CookieJar;

class UserSession
{
    private CookieJar $cookieJar;
    
    public function __construct()
    {
        $this->cookieJar = new CookieJar(
            '/',                // Chemin par défaut
            $_SERVER['HTTP_HOST'], // Domaine actuel
            isset($_SERVER['HTTPS']), // Secure si HTTPS
            true,               // HttpOnly pour la sécurité
            'Lax'               // SameSite par défaut
        );
    }
    
    public function login(int $userId, bool $remember = false): void
    {
        // Créer un token de session
        $sessionToken = bin2hex(random_bytes(32));
        
        if ($remember) {
            // Cookie qui dure 30 jours
            $this->cookieJar->setExpiringCookie(
                'remember_token',
                $sessionToken,
                30 * 24 * 3600, // 30 jours en secondes
                null,           // Utilise le chemin par défaut
                null,           // Utilise le domaine par défaut
                null,           // Utilise Secure par défaut
                true,           // HttpOnly pour la sécurité
                'Strict'        // SameSite Strict pour plus de sécurité
            );
        } else {
            // Cookie de session
            $this->cookieJar->setSessionCookie(
                'session_token',
                $sessionToken
            );
        }
        
        // Stocker aussi l'ID utilisateur
        $this->cookieJar->set(
            'user_id',
            (string) $userId,
            $remember ? time() + (30 * 24 * 3600) : 0
        );
        
        // Envoyer les cookies
        $this->cookieJar->send();
    }
    
    public function logout(): void
    {
        // Supprimer tous les cookies de session
        $this->cookieJar->remove('session_token');
        $this->cookieJar->remove('remember_token');
        $this->cookieJar->remove('user_id');
        
        // Envoyer les cookies supprimés
        $this->cookieJar->send();
    }
    
    public function getCurrentUser(): ?int
    {
        // Récupérer les cookies de la requête
        $cookies = CookieJar::fromRequest();
        
        if (isset($cookies['user_id'])) {
            return (int) $cookies['user_id'];
        }
        
        return null;
    }
}

Exemple 2 : Gestion des préférences utilisateur

use Npds\Cookie\CookieJar;

class UserPreferences
{
    private CookieJar $cookieJar;
    
    public function __construct()
    {
        $this->cookieJar = new CookieJar();
        
        // Configurer pour les préférences
        $this->cookieJar
            ->setDefaultPath('/')
            ->setDefaultHttpOnly(false) // Accessible en JavaScript
            ->setDefaultSameSite('Lax');
    }
    
    public function setTheme(string $theme): void
    {
        $this->cookieJar->setExpiringCookie(
            'theme',
            $theme,
            365 * 24 * 3600, // 1 an
            null,
            null,
            null,
            false, // Non HttpOnly pour permettre un accès JS
            'Lax'
        );
        
        $this->cookieJar->send();
    }
    
    public function setLanguage(string $language): void
    {
        $this->cookieJar->setExpiringCookie(
            'language',
            $language,
            365 * 24 * 3600,
            null,
            null,
            null,
            false,
            'Lax'
        );
        
        $this->cookieJar->send();
    }
    
    public function getPreferences(): array
    {
        $cookies = CookieJar::fromRequest();
        
        return [
            'theme' => $cookies['theme'] ?? 'light',
            'language' => $cookies['language'] ?? 'en',
        ];
    }
}

Exemple 3 : Cookies pour une API REST

use Npds\Cookie\CookieJar;

class ApiAuth
{
    private CookieJar $cookieJar;
    
    public function __construct()
    {
        $this->cookieJar = new CookieJar(
            '/api',
            'api.example.com',
            true,    // Toujours Secure pour les APIs
            true,    // HttpOnly pour la sécurité
            'None'   // SameSite=None pour les requêtes cross-site
        );
    }
    
    public function setAuthToken(string $token): void
    {
        // Token JWT avec expiration
        $this->cookieJar->setExpiringCookie(
            'auth_token',
            $token,
            3600, // 1 heure
            '/api/v1',
            'api.example.com',
            true,
            true,
            'None'
        );
        
        $this->cookieJar->send();
    }
    
    public function setCsrfToken(string $token): void
    {
        // CSRF token accessible en JavaScript
        $this->cookieJar->setSessionCookie(
            'csrf_token',
            $token,
            '/api',
            'api.example.com',
            true,
            false, // Non HttpOnly pour permettre l'accès JS
            'Strict'
        );
        
        $this->cookieJar->send();
    }
}

Exemple 4 : Gestion des cookies dans un middleware

use Npds\Cookie\CookieJar;

class CookieMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        // Créer un CookieJar avec les bonnes valeurs par défaut
        $cookieJar = new CookieJar(
            '/',
            $request->getHost(),
            $request->isSecure(),
            true,
            'Lax'
        );
        
        // Charger les cookies existants
        $cookieJar->load($request->cookies->all());
        
        // Ajouter le CookieJar à la requête
        $request->attributes->set('cookies', $cookieJar);
        
        $response = $next($request);
        
        // Envoyer les cookies dans la réponse
        $cookieJar->send();
        
        return $response;
    }
}

Bonnes pratiques

1. Sécurité des cookies de session

// BON - Cookie sécurisé
$secureCookie = new Cookie(
    'session',
    $token,
    time() + 3600,
    '/',
    'example.com',
    true,    // Secure (HTTPS uniquement)
    true,    // HttpOnly (inaccessible en JS)
    'Strict' // SameSite Strict
);

// MAUVAIS - Cookie non sécurisé
$insecureCookie = new Cookie(
    'session',
    $token,
    time() + 3600,
    '/',
    'example.com',
    false,   // Non Secure
    false,   // Accessible en JS
    'Lax'
);

2. Gestion des cookies cross-site

// Pour les APIs qui reçoivent des requêtes cross-site
$crossSiteCookie = new Cookie(
    'api_token',
    $token,
    time() + 3600,
    '/api',
    'api.example.com',
    true,    // Doit être Secure
    true,    // HttpOnly recommandé
    'None'   // SameSite=None pour cross-site
);

// Note: SameSite=None nécessite toujours Secure=true

3. Expiration appropriée

// Cookies de session - expirent à la fin du navigateur
$sessionCookie = Cookie::createSessionCookie('temp_data', 'value');

// Cookies de préférence - longue durée
$prefCookie = Cookie::createExpiringCookie('theme', 'dark', 365 * 24 * 3600);

// Cookies d'authentification - durée limitée
$authCookie = Cookie::createExpiringCookie('auth_token', $token, 2 * 3600);
// 2 heures

4. Validation des entrées

try {
    $cookie = new Cookie(
        $_POST['cookie_name'],
        $_POST['cookie_value'],
        time() + 3600
    );
} catch (InvalidArgumentException $e) {
    // Gérer l'erreur - nom de cookie invalide
    throw new Exception('Nom de cookie invalide: ' . $e->getMessage());
}

// Pour les valeurs utilisateur, utiliser rawurlencode()
$userValue = rawurlencode($_POST['user_data']);
$cookie = new Cookie('user_data', $userValue, time() + 3600);

// Ou utiliser un cookie Raw (attention aux caractères spéciaux)
$rawCookie = new Cookie(
    'raw_data',
    $_POST['data'],
    time() + 3600,
    null,
    null,
    null,
    null,
    null,
    true
);

API complète

Classe Cookie

Constructeur

public function __construct(
    string $name,
    string $value = '',
    $expires = 0,
    ?string $path = null,
    ?string $domain = null,
    ?bool $secure = null,
    ?bool $httpOnly = null,
    ?string $sameSite = null,
    bool $raw = false
)

Méthodes de fabrication statiques

  • createSessionCookie() - Crée un cookie de session
  • createExpiringCookie() - Crée un cookie avec expiration relative
  • createFromDate() - Crée un cookie avec expiration absolue

Getters

  • getName(): string
  • getValue(): string
  • getExpires(): int
  • getPath(): string
  • getDomain(): ?string
  • isSecure(): bool
  • isHttpOnly(): bool
  • getSameSite(): string
  • isRaw(): bool

Vérifications

  • isExpired(): bool
  • matches(string $path, string $domain, bool $secure): bool

Méthodes immuables

  • withValue(string $value): self
  • withExpires($expires): self
  • withPath(string $path): self
  • withDomain(?string $domain): self
  • withSecure(bool $secure): self
  • withHttpOnly(bool $httpOnly): self
  • withSameSite(?string $sameSite): self

Conversion

  • __toString(): string - Format pour l'en-tête Set-Cookie

Classe CookieJar

Constructeur

public function __construct(
    string $defaultPath = '/',
    ?string $defaultDomain = null,
    bool $defaultSecure = false,
    bool $defaultHttpOnly = true,
    string $defaultSameSite = Cookie::SAMESITE_LAX
)

Gestion des cookies

  • set() - Ajoute ou modifie un cookie
  • setSessionCookie() - Ajoute un cookie de session
  • setExpiringCookie() - Ajoute un cookie avec expiration relative
  • get() - Récupère un cookie par son nom
  • has() - Vérifie si un cookie existe
  • remove() - Marque un cookie comme expiré
  • clear() - Supprime tous les cookies
  • all() - Récupère tous les cookies

Information

  • count(): int
  • isEmpty(): bool

Filtrage et recherche

  • filterExpired(): self - Retourne un nouveau CookieJar sans les cookies expirés
  • match() - Trouve les cookies correspondant à une requête
  • load() - Charge des cookies depuis un tableau

Gestion des en-têtes

  • send(): void - Envoie tous les cookies au navigateur
  • fromRequest(): array - Récupère les cookies de la requête actuelle

Configuration des valeurs par défaut

  • setDefaultPath() - Définit le chemin par défaut
  • setDefaultDomain() - Définit le domaine par défaut
  • setDefaultSecure() - Définit Secure par défaut
  • setDefaultHttpOnly() - Définit HttpOnly par défaut
  • setDefaultSameSite() - Définit SameSite par défaut

Conclusion

Ce package offre une solution complète et sécurisée pour la gestion des cookies en PHP. Avec son API orientée objet, il facilite la création, la manipulation et l'envoi de cookies tout en respectant les meilleures pratiques de sécurité.

Les principales fonctionnalités incluent :

  • Support complet des attributs de cookies modernes
  • Gestion centralisée avec CookieJar
  • Validation robuste des données
  • API immuable pour une programmation plus sûre
  • Compatibilité avec les standards HTTP

Pour toute question ou problème, consultez la documentation ou ouvrez une issue sur le repository du projet.