Подтверждение регистрации по 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.

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