2010-06-27 6 views
34

Je voudrais ajouter une option de case à cocher « remember me » avant de vous connectersystème de connexion de PHP: Se souvenir de moi (cookie persistant)

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

Par exemple, Facebook ont ​​leur case « se souvenir de moi » afin que chaque fois que vous entrez facebook.com vous êtes déjà connecté.

Mon connexion actuelle utilise des sessions simples.

+0

Vous pouvez jeter un oeil à https://github.com/delight-im/PHP-Auth et sa source pour voir comment implémenter une fonction «se souvenir de moi» * secure *. Fondamentalement, il suffit de stocker une chaîne * très longue * (c'est-à-dire beaucoup d'entropie) de données aléatoires dans un cookie. Lorsque l'utilisateur visite votre page, vérifiez que "token" par rapport à votre base de données où vous suivez ces jetons. Si le jeton est valide, authentifiez l'utilisateur. – caw

Répondre

34

Cette question se pose beaucoup, voici quelques liens pour vous.

Il a également quelques grandes ressources rassemblées dans la réponse à cette question: The Definitive Guide To Website Authentication

+2

Meilleure solution: [Mise en œuvre de l'authentification sécurisée des utilisateurs dans les applications PHP avec une persistance à long terme] (https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence#title.2) (2015) –

32

Mise à jour (13/08/2017): Pour comprendre pourquoi nous séparer selector et token, au lieu d'utiliser un token, s'il vous plaît lire this article about splitting tokens to prevent timing attacks sur les requêtes SELECT.

Je vais extraire la stratégie décrite dans ce billet de blog about secure long-term authentication depuis qui couvre beaucoup de terrain et nous ne sommes intéressés par la partie "remember me".

Préambule - Base de données Structure

Nous voulons une table séparée de notre table des 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 choses importantes ici sont que selector et token sont des champs séparés.

Une fois connecté

Si vous n'avez pas random_bytes(), prenez juste 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) 
     ] 
    ); 
} 

réauthentifier Sur la page charge

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 (codées en base64 à 12 caractères) pour notre sélection. Cela fournit 72 bits de keyspace et donc 2 bits de résistance aux collisions (attaques d'anniversaire), ce qui est plus grand que notre capacité de stockage (integer(11) UNSIGNED) par un facteur de 16.

Nous utilisons 33 octets (264 bits) de 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é 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 empêcher 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 causer de baisse drastique des performances.

+1

Excellent travail. Cela vaut-il la peine d'ajouter le contrôle d'expiration dans votre code? Est-ce que vous régénérez le jeton ailleurs dans votre code (par exemple, 1 chargement de 10 pages)? – rybo111

+0

Argh! Cela semble très bien, mais je ne peux pas obtenir 'hash_equals' pour renvoyer true en utilisant l'une de ces fonctions personnalisées: https://php.net/hash_equals#115664 – rybo111

+0

Si vous avez besoin d'une implémentation in-PHP: https: // github. com/sarciszewski/php-future –

Questions connexes