Подтверждение регистрации по email на PHP


Этот материал я не могу назвать полноценной статьёй. Это, скорее, небольшая заметка с некоторыми моими размышлениями на тему регистрации. Ранее я уже рассказывал, как написать скрипт регистрации пользователей на сайте. На данный момент мы создаём один проект, где также понятие пользователей будет существовать. В связи с этим мы заново пишем систему регистрации и авторизации. Возможно, несколько позже, мы рассмотрим её код на нашем блоге, но сейчас я бы хотел поделиться своими размышлениями насчёт подтверждения регистрации. Я понимаю, что тема эта не нова, да и в интернете, наверняка, информации по ней море (честно, я ни разу не искал), но, думаю, лишней она не будет и, быть может, кому-то даже окажется полезной. Больше всего буду рад советам и обоснованной критике. Эта заметка — лишь мои размышления, которые, кстати, я сам реализовать буду лишь через пару дней. Тут будет минимум кода и чуть больше моих мыслей и идей.

Итак, суть проблемы… Пользователь регистрируется на сайте, заполняя все необходимые поля корректным образом. Данные, прежде чем добавиться в базу данных, проверяются нашими скриптами. Если всё корректно, то записываем данного юзера в табличку, скажем, users. Большинство полей, записанных в БД, нас попросту не волнуют. Остановиться стоит на логине, пароле и… кое о чём, о чём позже… Естественно, пароль в «чистом» виде хранить нельзя. Так как это теоретически может привести к краже паролей. Обычно пароль хранят в захэшированном виде, а при авторизации, пароль, введённый пользователем в текстовое поле, также хэшируют и сравнивают уже хэши, а не «чистые» пароли. Хэшируем функцией md5(). О данном алгоритме читаем тут, останавливаться на этом я не буду. Часто при хэшировании пароль «солят» — примешивают к оригинальному паролю какие-то случайные символы (причём, неизвестно куда именно – в начало пароля, середину или конец), а также хэшируют, например, дважды. Все эти меры позволяют свести к минимуму шанс расшифровки хэша вашего пароля злоумышленником, использующим, например, радужные таблицы. Соль, конечно же, также необходимо писать в базу данных, чтобы при каждой авторизации строить правильный хэш на основе вводимого юзером пароля. Также при регистрации обычно указывается email. В случае нашего проекта он является и логином. Естественно, почта у различных пользователей повторяться не может, поэтому в скрипте не забываем делать соответствующую проверку. И вот мы подобрались к сути проблемы – мы хотим реализовать подтверждение регистрации по email. Зачем это нужно? Плюсов можно найти много. Все они сводятся к тому, что ты подтверждаешь, что email действительно твой. А это нам позволит в будущем реализовать, скажем, «восстановление» пароля (кавычки использованы неспроста – ведь пароль из хэша восстановить не получится, обычно новый пароль либо вводит сам пользователь в специальной форме, либо генерируется автоматически, высылаясь на почту забывчивого юзера). Также это исправит нас от жалоб тех пользователей, чьи адреса использовали их недруги при регистрации, ведь им может приходить нежелательная рассылка с нашего сайта. Рассылка также является одной из причин важности подтверждения по email.

Пользователь зашёл на сайт, зарегистрировался, увидел сообщение о том, что запрос на подтверждение отправлен ему на почту. Что он должен увидеть в письме? Это будет ссылка на определённую страницу, где и будет проводиться верификация. Идея заключается в том, что ссылка должна содержать что-то уникальное, что абсолютно невозможно узнать, не прочитав письма. Ведь если при регистрации я указал не свой email, то и узнать эту «уникальность» я никак не смогу, не имея доступа к ящику. Так, предположим, что почта наша, и мы открыли письмо. Нечто уникальное – некоторый случайно сгенерированный get-параметр. Ссылка подтверждения имеет следующий вид:

http://example.com/verification/?hash=9kzhrz3b34sb

«9kzhrz3b34sb» — случайно сгенерированный параметр, например, подобной функцией:

1
2
3
4
5
6
7
8
9
10
11
12
<? function generateCode($length = 8)
{
   $chars = 'abdefhiknrstyz1234567890';
   $numChars = strlen($chars);
   $string = '';
   for ($i = 0; $i < $length; $i++)
   {
      $string .= substr($chars, rand(1, $numChars) - 1, 1);
   }
   return $string;
}
?>

Функция в качестве параметра принимает длину строки, которую необходимо сгенерировать. Символы, необходимые при генерации указать можно в переменной $chars.

Итак, нас попросили перейти по этой ссылке, мы так и сделали. Скрипт принимает параметр hash. Что делаем дальше? Дело в том, что этот параметр был создан в момент регистрации и должен был быть записан в БД рядом с логином, солью, паролем и другими полями данных пользователя. Значит, ищем пользователя с таким значение в поле hash? Но как нам узнать, что ты – это тот же самый человек, который заполнял поля регистрации? Вдруг тебе на почту пришла эта ссылка, а на самом деле ты и про сайт-то такой не слышал? Первая мысль, которая меня посетила для решения данной проблемы – сессия, в которую мы запишем что-то связывающее нас с записью в БД. Тут есть некоторый простор для фантазии. Пусть в нашем случае этим «что-то» будет хэш пароля. То есть, теперь ситуация следующая: при регистрации создалась уникальная строка, которая вместе с другими данными записалась в БД, а также отправилась в качестве get-параметра ссылки к нам на электронный ящик. Кроме этого в сессию записался и хэш нашего пароля. Мы переходим по ссылке, скрипт ищет запись в БД с такой уникальной строкой. Если нашлась такая строка, то сравниваем хэш пароля, хранящийся в базе, с хэшем, висящим в переменной сессии. Если они совпали, то можно утверждать следующие – у тебя есть доступ к почте (как иначе узнать параметр?) и именно ты производил регистрацию (иначе не было бы у тебя в сессии хэша твоего же пароля). Сессию, конечно, можно и украсть (правильнее сказать – «украсть идентификатор сессии»), но это уже зависит от беспечности пользователя и является другой историей, тем более, что украв сессию, без уникального параметра, высланного на почту, нам не обойтись.

Казалось бы – всё! Но эта идея прожила в моей голове лишь пару секунд. Нетрудно заметить один существенный недостаток данного механизма – подтвердить свои права можно лишь пока сессия жива. Когда её не станет, мы никак не сможем определить, тот ли это пользователь, который регистрировался на нашем сайте. Встаёт вопрос – где нам в таком случае хранить хэш пароля или каких-либо других данных, конкретных для данного юзера? Почему бы не в том же письме? Модифицируем ссылку:

http://example.com/verification/?hash=9kzhrz3b34sb&md5=bcbf73b8a48c9805bcb110b8aa089a52

Параметр md5 может быть чем-то вроде:

$md5 = md5($login.md5($password.$salt).$mail)

То есть, хэшем любых данных, уже хранящихся в базе данных.

Перейдя по ссылке мы сравним оба параметра с аналогичными в базе данных и с уверенностью сможем сказать, что именно этот пользователь регистрировался на нашем сайте и email на самом деле принадлежит ему. Преимущество данного способа над предыдущим состоит в том, что никакой сессии не существует и подтвердить регистрацию можно будет в любое время.

Пожалуй, стоит остановиться на том, что произойдёт после подтверждения. Наверняка, до этого у пользователя либо вообще не будет прав на сайте, либо они будут существенно ограничены. Как это будем запоминать? С этой задачей прекрасно справится ещё одно поле в БД, скажем, verification, которое до момента подтверждения регистрации для конкретного пользователя будет равно false или, например, нулю. После подтверждения просто меняем это поле на true.

Ещё раз напомню – это лишь мои рассуждения на данную тему. Возможно, я что-то упустил. Если так, буду рад услышать это в комментариях к статье. Вдруг вы сможете предложить что-то лучше.

  • Спасибо, большое!

  • Полезная статья, спасибо автору!

  • Юзер

    Отказываясь от сессий и добавляя новые параметр в get, вы все равно не сможете определить что именно тот человек регистрировался, к которому пришло письмо.

  • Вячеслав

    я сделал так — в сессию пишем ‘e_ящик@почта.ру’ => ‘случайный хэш’, отправляем на мыло ссылку содержащую хэш и емаил, при ее открытии просто сравниваем хэш из урла с тем, что в сессии (обязательно проверив не пустые ли параметры), чем плох такой способ?

  • Дмитрий

    Так пусть после подтверждения он должен будет залогинится. а вот хеши пароля, хоть с salt, хоть с email в письмо слать не надо. Ибо хакер может узнать каким образом составлен этот хеш.

  • Александр

    Я сегодня писал скрипт для себя , и искал варианты как сделать мне на отсылку и проверку …
    Посмотрел кодик от DLE движка и там оказалось что всё отправляется через hash , далее вытаскивает и расшифровывает . Если результат true заноситься данные в БД .
    Сейчас собрал себе конструктор и мне интересно кто будет сидеть и расшифровывать мой хэш ?
    hash.php?url=cm91dGVyX3NrbmE0NTFfNTRkczMyZjEzMnNkMTU0c3M0NzcxYV9hZG1pbkB0cnVlLWNvZGVyLnJ1
    Да кстати в данный хеш засунул адрес сайт .
    Если получиться пишите на мыло , я оставил при ответе .

  • moreo

    что-то я не понял автора (второй способ, без сессии), каким образом он идентифицирует что именно тот пользователь перешел по ссылке, который вводил данные пр регистрации, записал в теле письма hash паролей и др данных, и что? пришло мне это пиьсмо (допустим НЕ я вводил данные), перешел по ссылке, дальше?

    Перейдя по ссылке мы сравним оба параметра с аналогичными в базе данных и с уверенностью сможем сказать, что именно этот пользователь регистрировался на нашем сайте и email на самом деле принадлежит ему.

    эти данные, в ссылке, и в базе, всегда будут равны, или я чего-то не понимяю?)

  • Сергей

    Почему не куки?

  • MiRaX

    Автор допускает серьезную логическую ошибку в описании валидации пользователя без сессии. На самом деле идентифицировать подлиность пользователя по ссылке высланной на почту невозможно! Варианты с использованием сессии или куки ненадежны, так как они могут быть уничтожены в любой момент. Именно поэтому в письмах приходит сообщение типа: Вы зарегистрировались там-то… Если это были не вы, просто игнорируйтеэ то письмо.

  • MiRaX

    Да, и еще интересует вот какой момент. Кто-то зарегистрировался на сайте используя мой емайл. Этот кто-то естественно ввел какой-то пароль. Мне пришло письмо, я естественно игнорировал письмо, потому, что регистрировлся не я. Проходит время. Я хочу зарегистрироваться на том же сайте. Но мой емайл уже зарегистрирован с каким-то паролем и все еще неподтвержден. Как вам такая ситуация, и какие могут быть выходы из нее.

    • Обычно, если вовремя не активировать аккаунт, а попытаться сделать это позже, пишет, что, мол, «ссылка для активации устарела». Ну, примерно так. Можно попросту ввести временной лимит. Например, если пользователь, воспользовавшийся для регистрации чужим емэйлом, в течение, например, часа не подтвердит регистрацию, будет возможна повторная регистрация с такой же почтой. То есть, через час вы сможете без проблем зарегистрироваться по той же почте, несмотря на то, что запись с таким емэйлом в базе данных уже имеется (а может и отсутствует — можно ведь кроном каждый час, например, запускать скрипт, который будет удалять неактивированные аккаунты). Это первое, что приходит в голову. Не знаю, насколько удачный это вариант, ибо он имеет свои небольшие недочёты. Например, в этот час вы никак сами не сможете зарегистрироваться. А вообще да, тут скользкий момент, надо глубже подумать.

      • Настя Крайность

        Пришло письмо с некоего сайта, о том что я якобы там зарегистрировался. При этом ссылки для подтверждения нет! Просто констатация факта. На сайте предлагают заблокировать аккаунт(e-mail), но при этом удалить e-mail из базы или же удалить ПРОФИЛЬ(!) возможности нет!!
        Т.е. сайт для регистрации требует только e-mail и тот без подтверждения владельца.
        Что делать в таком случае?

  • sash

    а мне так обидно, у меня на сайте не регистрируются даже боты.. http://web4myself.ru/