littleguga
@littleguga
Не стыдно не знать, а стыдно не интересоваться.

Как правильно написать авторизацию/аутентификацию?

Что можно почитать для того, чтобы понять, как правильно всё организовать?

1. С полного нуля. Пользователь вводит логин + пароль:
1.1 Что писать в куки/сессию?
1.2 Как на каждой странице организовать проверку авторизован пользователь или нет?
1.3 Если нужно, чтобы у пользователя был только одновременный доступ с одного устройства - как быть?
1.4 А если с нескольких?

2. Пользователь входит через ВК
1.1 Что писать в куки/сессию?
1.2 Как на каждой странице организовать проверку авторизован пользователь или нет?

Большая просьба - ответить на все пункты, либо дать ресурс/статью/где/что прочитать, чтобы ответить на все пункты.

Заранее благодарен за информативный ответ!
  • Вопрос задан
  • 28651 просмотр
Пригласить эксперта
Ответы на вопрос 8
dasha_programmist
@dasha_programmist
ex Software Engineer at Reddit TS/React/GraphQL/Go
Есть два варианта хранения данных об авторизованном пользователе:
1) В куки (так по умолчанию используется в асп.нет): необходимые данные (claims) шифруются machineKey и отдаются пользователю в http-only куках, таким образом при каждом запросе на сервер они присылаются, расшифровываются и далее можно проверить в необходимых местах.
плюсы: полностью stateless, нет надобности обращаться к БД
минусы: при необходимости "выбить" сессию со стороны сервера нужно поднимать более сложную логику и хранить флаги в промежуточном хранилище (проверять что если для такого-то пользователя требуется завершить, то такие действия, иначе другие);
2) Ключ сессии: после успешной аутентификации авторизуем пользователя и claims храним на сервере в быстрой памяти или БД (key-value), где ключ - ключ сессии, значение - любые данные.
плюсы: есть полный контроль состоянием авторизации (как и возможность завершить сессию со стороны сервера, так и сменить пользователю роль(или другие параметры) "на лету")
минусы: организация доп. прослойки - кэша или хранение в БД (медленно), при перезапуске/падении сервиса сессии клиентам потребуется перелогиниться.

1
1.1 В куки писать или ключ сессии или шифрованные данные о пользователе, сессия - абстрактное понятие (это пара: ключ и данные), ключ должен быть защищенным, т.е. трудным к копированию (хотя бы зрительно трудно запомнить), уникальным (чтобы не возникло коллизий: двум разным пользователям выдался один и тот же ключ, т.е. это не должна быть хэш-функция от логина-пароля или IP или чего-то неуникального).
1.2 В асп.нет существуют атрибуты авторизации (в которых можно расставлять проверки на требование таковой, роль, конкретный пользователь), в общем смысле логика такова: поступил запрос на сервер, далее нужно посмотреть к какому ресурсу идёт обращение (защищенному или свободному), если ресурс защищен, то проверить куки (ключ сессии или шифрованные данные), расшифровать/получить данные о сессии из кэша и предпринять решение: пускаем или не пускаем (отдаём 401/403 или отдаем 200/404/...).
1.3 Завести на сервере (в кэше или БД) словарь , при алгоритме проверки сессии добавить условие проверки на наличие записи в словаре.
1.4 С нескольких - словаря не нужно.

2
2.1 Даже если пользователь входит через ВК всё равно нужно отдавать свои ключи сессий/шифрованные данные, а вот внутри данных уже хранить access_token от вк-шной сессии, так очень маленькая вероятность, что токен ВК утечет, а если утек ключ сессии, то действия будут ограничены только функционалом сайта.
2.2 После расшифровки куки или данных по ключу сессии делать доп запрос на сервер ВК с токеном, который сохранился при аутентификации (access_token), запрос простой, например получить имя пользователя, если ВК выдал что токен просрочен или ошибку, то сессию закрывать или куки с данными обнулять.
Ответ написан
@enibeniraba
Очень сильно упрощенно, без ООП и паттернов):
Авторизация:
session_start();
if (!empty($_SESSION['user_id']))
	die('Вы уже авторизованы');
	
$user = get_user_by_login($_POST['login']);
if (!$user)
	die('Пользователь не найден');
	
if ($_POST['pass'] !== $user['pass'])
	die('Неверный пароль');

$_SESSION['user_id'] = $user['id'];
die('Привет, '.$user['login']);

При этом в куки пользователя запишется session_id, а в сессию user_id.

Проверка:
session_start();
if (empty($_SESSION['user_id']))
	die('Нет прав');

$user = get_user_by_id($_SESSION['user_id']);
if (!$user || !$user['active'])
{
	unset($_SESSION['user_id']);
	die('Нет прав');
}


Выход:
session_start();
unset($_SESSION['user_id']);


Если нужен доступ только с одного устройства (последнего, с которого был вход), то храним на сервере последнюю сессию пользователя и при каждом запросе:
if ($user['session_id'] !== session_id())
{
	unset($_SESSION['user_id']);
	die('Нет прав');
}


Если с нескольких, то делать ничего не нужно.

При oauth авторизации через внешние сервисы в сессии и куках будет все то же самое.
В бд добавятся 2 поля auth_provider и auth_provider_id.
В auth_provider у тебя будет vk или fb или google... в auth_provider_id - id пользователя в vk, fb...
Когда юзер нажимает кнопку войти через vk, ты его редиректишь на контакт. Там он что-то делает. Потом контакт его редиректит к тебе с ?code=request_code.
Ты используя code делаешь запрос к api контакта на получение access_token.
Получив access_token, делаешь запрос к api на получение инфы о пользователе. В этой инфе должен быть какой-нибудь уникальный id (auth_provider_id).
Если у тебя нет пользователя с таким auth_provider_id для провайдера vk, то создаешь его либо без логина, либо генерируешь что-нибудь, без пароля.
Если пользователь был создан или он был до этого, помещаешь в сессию user.id и считаешь пользователя авторизованным.

PS:
По каждой строчке ответа есть нюансы, но думаю общая картина такая.
Естественно лучше написать класс для пользователя, чтобы использовать что-то вроде:
$user->is_auth();
$user->auth_by_id($user_id);
$user->unauth();
Ответ написан
deemytch
@deemytch
linux root, ruby/perl programmer, sql, backend.
Если совсем коротко, то суть авторизации заключается в том, что пользователь взамен своего пароля и логина получает какую-то длинную малопредсказуемую строку, которую хранит все время сессии на своей стороне и предъявляет серверу на каждый чих. И кроме сервера эту строку никому-никому.

А сервер хранит эту строку у себя, с привязкой к записи пользователя.

Дополнительные детали - это хранение этой строки с ограничением по времени, с привязкой к IP адресу, версии браузера и другим деталям, с тем, чтобы можно было регулировать количество одновременных сессий, время сессии, и пытаться обнаружить воровство этой строки. Строка обычно называется хэш, хотя не факт. Строку можно не хранить, а сверять с другим хэшои, получаемым из первого. И так далее по мере роста фантазии и извращённости мыслей.
Ответ написан
Комментировать
С полного нуля. Пользователь вводит логин + пароль:
Пользователь входит через ВК


Не имеет особого значения, ваша задача полученную информацию замапать на ID пользователя в ВАШЕЙ базе, с учетом необходимых проверок. ВК будет считаться доверенной стороной, т.е. ему вы доверяете процедуру проверки аутентичности юзера. Если аутентификация на вашей стороне - то проверяете вы (проверяете пароль по соленому хешу в базе, ну или как-то еще). Если у вас "быстрый вход" по ВК (без регистрации), то нужно сделать еще авторегистрацию, если userid не найден по данным от ВК. Правда, тут нужно быть готовым, что юзер захочет слинковать свои аккаунты, если он уже зарегался по логину/паролю. Ну или хотя бы предупредить при входе по ВК, что он в базе не найден и будет создан новый акк, чтобы он не удивлялся потом.

Что писать в куки


идентификатор сессии. Как уже сказали, должен быть сложным для копирования (скопированный идентификатор сессии - украденная сессия), уникальным. Погуглите алгоритмы или воспользуйтесь стандартыми. Сессии хранить или стандартными средствами, или попробовать редиску (там есть авто-expire, что приятно).

Если нужно, чтобы у пользователя был только одновременный доступ с одного устройства - как быть?

проверять наличие сессии для данного пользователя (по ID пользователя с этапа аутентификации). Если сессия уже есть - убивать ее, создавать новую (новое устройство успешно зайдет, старое - "разлогинится"). Юзеру правда ничего не помешает скопировать идентификатор сессии из кукисов на другой девайс, так что можете еще в сессию IP писать, или user-агента (поменялся - пересоздаем сессию).

А если с нескольких?

ничего дополнительно не проверять, допусть создание любого количества сессий.

Как на каждой странице организовать проверку авторизован пользователь или нет?

идем в хранилище сессий (стандартные механизмы PHP/Redis), запрашиваем/стартуем сессию по идентификатору, пришедшему в куках. Если такой сессии нет (устарела, либо никогда и не было, идентификатор юзер сам придумал) - авторизацию не выполняем. Если сессия есть - то в зависимости от того, что мы там храним, либо достаем USERID, и пробиваем по основной БД его права, либо достаем права из самой сессии, если мы их там закешировали. "Выдача" прав - есть процедура авторизации. Теперь в зависимости от набора прав - меняем логику внутри скрипта. В больших системах применяют понятия ролей и групп - это все можно погуглить. Вкратце - при авторизации определяется, в каких группах состоит пользователь. Затем, по множеству групп сопоставляются роли, которые "играет" пользователь в системе (администратор данных/пользователей, администратор бэкапа, главбух, менеджер и т.д. и т.п.). Роли фиксированы, и зависят от логики приложения - в зависимости от имеющихся у пользователя ролей меняется и поведение приложения.
Также сейчас можно встретить claim-based подход, особенно в asp.net - тоже погуглите.

Что хранить в сессии?

Как минимум - USERID, но можно еще и закэшировать информацию о его правах, чтобы не читать постоянно ее из основной базы. Плюс, тут же хранить данные, относящиеся к САМОЙ сессии, например тот же IP входа, время входа и т.д. Тут зависит от задачи.
Ответ написан
Комментировать
Здесь еще нужно очень хорошо разделять понятия авторизация и аутенфикация.

Аутенфикация - это определения пользователя, то есть, что это за пользователь. Обычно этот этап проходит сразу же после ввода логина пароля. Если логин верный, ты мы и сразу можем сказать: "Пользователь аутенфицырован". К примеру, если использовать HTTP Basic аутенфикацию, то в случае не верного логина/пароля будет ошибка 401.

Авторизация - это проверка прав пользователя к определенному ресурсу. К примеру, может ли пользователь редактировать какой-то материал (статью, коммент), может ли он просматривать какой-то ресурс.

Очень часто еще может "всплыть" понятие "Фаервол" - это механизм определения для поведения системы аутенфикации/авторизации. К примеру: В личном кабинете, необходимо чтобы пользователь был обязательно авторизован, но вот на сайте (доки, другие страницы), нет. В результате, мы можем создать два фаервола, определяющих это поведение для разных URL-ов.

Два этих механизма могут работать как угодно, так как Вы захотите, главное, это скажим соблюдать некие правила:
1. Ни в коем случае не аутенфицировать по UserID, иначе, любой сможет получить доступ. К примеру: Вы сохраняете в куки только UserID для аутенфикации, тогда, кто-то сможет подменить со своей стороны куку, и ввойти под другим пользователем.
2. Не храните в куках логин/пароль. Так как есть множество вирусов, которые могут прочитать куки и отправить на другой сервере.
3. Пароль в БД должен быть хеширован, и проверка должна быть именно по хешам, а не по реальным паролям. Иначе, если кто-то у Вас свиснит БД, то очень вероятно, что и сможет получить доступ к множествам другим аккам, так как множество людей используют один и тот же пароль.

Лично для своих проектов, я всегда использую Symfony Security, так как в этом пакете уже все сделано место Вас. Главное это верно подключить и настроить, и вауля :)
Ответ написан
@aqwAntonio
не хочу занудствовать, но еще не рассмотрен вопрос защиты от фиксирования идентификатора сеанса, в этом случае функция session_regenerate_id() генерирует новый идентификатор сессии текущего посетителя. само же значение идентификатора сессии в куки устанавливается функцией session_start(), а вам ее в любом случае надо вызвать, иначе сессии не будут работать

т.е. я хочу сказать, что пытаясь придумать какое то собственное решение вместо использования системных решений, вы рискуете в будущем что то забыть или не учесть по незнанию и необходимость использования такой функции как, например, session_regenerate_id() , вызовет у вас потом головную боль

поэтому по порядку:
1.1 Что писать в куки/сессию?
ничего вручную в куки писать не нужно, вызываете session_start().
в сессию можете записать значение is_auth = true после того, как проверите, что логин и пароль верные

1.2 Как на каждой странице организовать проверку авторизован пользователь или нет?
ну это впрос больше архитектуры приложения, чем вопрос только авторизации. для пары страниц можно сделать общий подключаемый файл проверки авторизации, а для среднего проекта лучше сразу использовать фреймворк, но тогда вопросы об авторизации у вас вероятно изменятся, т.к. во фреймворках обычно уже заложен базовый функционал авторизации. в самом простом случае проверяете что значение сессии is_auth === true

1.3 Если нужно, чтобы у пользователя был только одновременный доступ с одного устройства - как быть?
ну это довольно сложно но попробую объяснить вкратце
- во первых, нужно хранить актуальный идентификатор сессии пользователя в постоянном хранилище, БД или еще где либо (т.е. на каждой странице после проверки авторизации обновляете идентификатор сессии в БД)
- во вторых, вам нужно хранить сессии авторизации в определенной папке, заданной функцией session_save_path($dir). смысл в том, что папка $dir должна быть доступна вам для записи чтения (изучите вопрос безопасности отдельно)
- в третьих, после успешной авторизации вы извлекаете из БД идентификатор сессии данного пользователя (см п. 1) и удаляете эту сессию из директории (см п. 2), и только потом записываете новый идентификатор сессии пользователя в БД (снова см. 1)

1.4 А если с нескольких?
тоже самое что в 1.3, только раньше у вас была структура таблицы допустим user_id, session_id, а в данном случае будет user_id,session_id, device_id т.е. смысл в том, что это не таблица пользователей а таблица сессий пользователей

Тажк рекомендую прочитать книгу "PHP. Рецепты программирования" от издательства O'Reilly, второе издание или более позднее

По второму вопрос в сущности ничего не меняется, кроме того, что дополняется функционалом инициализации и обработки ответов от vk.com. об этом можете почитать на странице https://vk.com/dev/auth_sites
Ответ написан
Комментировать
@alexey_abramov
Если еще требуется то в простой и понятной форме как создать авторизацию на куках расписано здесь - habrahabr.ru/post/13726
Ответ написан
Комментировать
@nozzy
Symfony, Laravel, SQL
1. рекомендую начать с библиотеки userPie, посмотреть код, мне очень помогло понять всю эту кухню.
userpie.com
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы