Система входа в PHP: Запомнить меня (постоянный cookie)

Прежде чем войти в систему, я хочу добавить флажок «запомнить меня».

Каков наилучший способ безопасного хранения файла cookie в браузере пользователя?

Например, у Facebook есть флажок «запомнить меня», чтобы каждый раз, когда вы вводили facebook.com, вы уже вошли в систему.

Мой текущий логин использует простые сеансы.

Этот вопрос задают много, вот некоторые ссылки для вас.

  • Лучшая практика для внедрения Secure «Запомнить меня»
  • «Помни меня на этом компьютере» – как это должно работать?
  • «Keep Me Logged In» – лучший подход

Кроме того, в ответе на этот вопрос собраны некоторые большие ресурсы: окончательное руководство по аутентификации веб-сайта

Обновление (2017-08-13) . Чтобы понять, почему мы разделяем selector и token , вместо того, чтобы просто использовать token , прочитайте эту статью о разделении токенов, чтобы предотвратить временные атаки на запросы SELECT.

Я собираюсь извлечь стратегию, изложенную в этом сообщении в блоге о безопасной долгосрочной аутентификации, поскольку это покрывает много места, и нас интересует только часть «помни меня» .

Преамбула – Структура базы данных

Нам нужна отдельная таблица из таблицы наших пользователей, которая выглядит так: (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`) ); 

Здесь важны то, что selector и token являются отдельными полями.

После входа в систему

Если у вас нет random_bytes() , просто возьмите копию 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('Ymd\TH:i:s', time() + 864000) ] ); } 

Повторная аутентификация на странице загрузки

 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 } } 

Детали

Мы используем 9 байтов случайных данных (base64, закодированных до 12 символов) для нашего селектора. Это обеспечивает 72 бита пространства ключей и, следовательно, 2 36 бит сопротивления столкновению (атаки на день рождения), который больше, чем наша емкость ( integer(11) UNSIGNED ) в 16 раз.

Мы используем 33 байта (264 бит) случайности для нашего фактического аутентификатора. Это должно быть непредсказуемым во всех практических сценариях.

Мы храним SHA256 хэш аутентификатора в базе данных. Это уменьшает риск олицетворения пользователя после утечки информации.

Мы пересчитываем хэш SHA256 значения аутентификатора, хранящегося в cookie пользователя, а затем сравниваем его с сохраненным хэшем SHA256 с использованием hash_equals() для предотвращения временных атак.

Мы отделили селектор от аутентификатора, потому что поиск БД не является постоянным. Это устраняет потенциальное влияние временных утечек на поиск, не вызывая резкого падения производительности.