Раз уж подняли эту тему, хочется напомнить о том, как улучшить реализацию стандартной функции remember-me.
Оба предложенных ранее варианта не учитывают один важный момент, что, если token будет похищен? При обычной реализации аутентификации через remember me token, атакующий, получив такой токен, получит доступ к сайту на неограниченное время, а жертва даже не узнает о факте хищения…
Barry Jaspan предложил улучшенный вариант remember-me аутентификации.
Вкратце, добавляется еще один тип токена — series. Генерировать его нужно случайно, можно так же, как и обычный token. Главное отличие его от токена, это то, что он не меняется после успешной аутентификации через токен.
На примере кода от zerkms:
Таблица для хранения:
И класс с примером
У пользователя в куках сохраняются token и series, скрипт сверяет их с теми, что предоставлены в базе данных. Если они совпадают, то аутентификация успешна. Пользователь получает новый token с предыдущим series. Если token разный, а series один и тот же, то удаляем все remember-me записи для этого аккаунта и сообщаем пользователю о том, что, возможно, его токен был похищен.
ps: это не готовое решение, а пример.
Оба предложенных ранее варианта не учитывают один важный момент, что, если token будет похищен? При обычной реализации аутентификации через remember me token, атакующий, получив такой токен, получит доступ к сайту на неограниченное время, а жертва даже не узнает о факте хищения…
Что же делать?
Barry Jaspan предложил улучшенный вариант remember-me аутентификации.
Вкратце, добавляется еще один тип токена — series. Генерировать его нужно случайно, можно так же, как и обычный token. Главное отличие его от токена, это то, что он не меняется после успешной аутентификации через токен.
На примере кода от zerkms:
Таблица для хранения:
CREATE TABLE test.one_time_auth( token CHAR (32), series CHAR (32), user_id INT (11) UNSIGNED NOT NULL, expire DATETIME DEFAULT NULL, PRIMARY KEY (series) ) ENGINE = INNODB
И класс с примером
<?php $db = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', ''); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $auth = new one_time_auth($db); list($token, $series) = $auth->remember(10, null, '2010-12-31'); $user_id = $auth->remind($token, $series); list($token, $series) = $auth->remember(10, $series, '2010-12-31'); $user_id = $auth->remind($token, $series); echo $user_id; list($token, $series) = $auth->remember(10, $series, '2010-12-31'); try { $user_id = $auth->remind('wrongone', $series); } catch (ThiefAssumedException $e) { echo 'We think your cookie was stolen. Please, log in again.'; } $user_id = $auth->remind('wrongone', 'wrongone'); // do nothing class ThiefAssumedException extends Exception {} class one_time_auth { /** * @var PDO */ private $db; public function __construct(PDO $db) { $this->db = $db; } public function remember($user_id, $series = null, $expire = null) { $sql = 'INSERT INTO one_time_auth (token, series, user_id, expire) VALUES (:token, :series, :user_id, :expire)'; $stmt = $this->db->prepare($sql); while (true) { try { $stmt->execute(array( ':token' => $token = $this->generateToken(), ':series' => $series = $series == null ? $this->generateToken() : $series, 'user_id' => $user_id, 'expire' => $expire )); break; } catch (PDOException $e) {} } return array($token, $series); } public function remind($token, $series) { $sql = 'SELECT user_id, token FROM one_time_auth WHERE series = :series AND (expire IS NULL OR expire >= NOW()) LIMIT 1'; $stmt = $this->db->prepare($sql); $stmt->execute(array('series' => $series)); if ($row = $stmt->fetch()) { if ($row['token'] != $token) { $stmt = $this->db->prepare('DELETE FROM one_time_auth WHERE user_id = :user_id'); $stmt->execute(array('user_id' => $row['user_id'])); throw new ThiefAssumedException(); } $stmt = $this->db->prepare('DELETE FROM one_time_auth WHERE series = :series'); $stmt->execute(array('series' => $series)); return $row['user_id']; } } private function generateToken() { return md5(uniqid('', true)); } }
Как это работает?
У пользователя в куках сохраняются token и series, скрипт сверяет их с теми, что предоставлены в базе данных. Если они совпадают, то аутентификация успешна. Пользователь получает новый token с предыдущим series. Если token разный, а series один и тот же, то удаляем все remember-me записи для этого аккаунта и сообщаем пользователю о том, что, возможно, его токен был похищен.
ps: это не готовое решение, а пример.
