Pull to refresh

Входите! Аутентификация без логина и пароля, «запомнить меня»

Reading time3 min
Views13K
Раз уж подняли эту тему, хочется напомнить о том, как улучшить реализацию стандартной функции remember-me.

Оба предложенных ранее варианта не учитывают один важный момент, что, если 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: это не готовое решение, а пример.
Tags:
Hubs:
Total votes 13: ↑9 and ↓4+5
Comments20

Articles