web-dev-qa-db-fra.com

PHP système de connexion: Remember Me (cookie persistant)

Je souhaite ajouter une case à cocher "Se souvenir de moi" avant de me connecter.

Quelle est la meilleure façon de stocker en toute sécurité un cookie dans le navigateur de l'utilisateur?

Par exemple, Facebook a sa case à cocher "se souvenir de moi" de sorte que chaque fois que vous entrez sur facebook.com, vous êtes déjà connecté.

Ma connexion actuelle utilise des sessions simples.

41
Karem

Mise à jour (2017-08-13) : Pour comprendre pourquoi nous séparons selector et token, au lieu de en utilisant simplement un token, veuillez lire cet article sur le fractionnement des jetons pour empêcher les attaques de synchronisation sur les requêtes SELECT.

Je vais extraire la stratégie décrite dans ce billet de blog sur l'authentification sécurisée à long terme car cela couvre beaucoup de terrain et nous ne sommes intéressés que par le "souvenez-vous de moi" pièce.

Préambule - Structure de la base de données

Nous voulons une table distincte de la table de nos utilisateurs qui ressemble à ceci (MySQL):

CREATE TABLE `auth_tokens` (
    `id` integer(11) not null UNSIGNED AUTO_INCREMENT,
    `selector` char(12),
    `token` char(64),
    `userid` integer(11) not null UNSIGNED,
    `expires` datetime,
    PRIMARY KEY (`id`)
);

Les points importants ici sont que selector et token sont des champs séparés.

Après la connexion

Si vous n'avez pas random_bytes(), récupérez simplement une copie de random_compat .

if ($login->success && $login->rememberMe) { // However you implement it
    $selector = base64_encode(random_bytes(9));
    $authenticator = random_bytes(33);

    setcookie(
        'remember',
         $selector.':'.base64_encode($authenticator),
         time() + 864000,
         '/',
         'yourdomain.com',
         true, // TLS-only
         true  // http-only
    );

    $database->exec(
        "INSERT INTO auth_tokens (selector, token, userid, expires) VALUES (?, ?, ?, ?)", 
        [
            $selector,
            hash('sha256', $authenticator),
            $login->userId,
            date('Y-m-d\TH:i:s', time() + 864000)
        ]
    );
}

Nouvelle authentification au chargement de la page

if (empty($_SESSION['userid']) && !empty($_COOKIE['remember'])) {
    list($selector, $authenticator) = explode(':', $_COOKIE['remember']);

    $row = $database->selectRow(
        "SELECT * FROM auth_tokens WHERE selector = ?",
        [
            $selector
        ]
    );

    if (hash_equals($row['token'], hash('sha256', base64_decode($authenticator)))) {
        $_SESSION['userid'] = $row['userid'];
        // Then regenerate login token as above
    }
}

Détails

Nous utilisons 9 octets de données aléatoires (base64 encodé à 12 caractères) pour notre sélecteur. Cela fournit 72 bits d'espace de clés et donc 236 bits de résistance aux collisions (attaques d'anniversaire), ce qui est supérieur à notre capacité de stockage (integer(11) UNSIGNED) par un facteur de 16.

Nous utilisons 33 octets (264 bits) de caractère aléatoire pour notre authentificateur réel. Cela devrait être imprévisible dans tous les scénarios pratiques.

Nous stockons un hachage SHA256 de l'authentificateur dans la base de données. Cela réduit le risque d'usurpation d'identité de l'utilisateur suite à des fuites d'informations.

Nous recalculons le hachage SHA256 de la valeur d'authentificateur stockée dans le cookie de l'utilisateur, puis le comparons avec le hachage SHA256 stocké en utilisant hash_equals() pour éviter les attaques de synchronisation.

Nous avons séparé le sélecteur de l'authentificateur car les recherches de base de données ne sont pas à temps constant. Cela élimine l'impact potentiel des fuites de synchronisation sur les recherches sans provoquer de perte de performances drastique.

47
36
Paul Dixon