Система аутентификации: сделай сам vs возьми готовое
Разработка системы аутентификации может показаться начинающему разработчику простой задачей. Пользователь создает учетную запись, данные сохраняются, и в дальнейшем по логину-паролю происходит вход. Но когда начинаешь копать глубже, система аутентификации, точно луковица, открывает всё новые слои. В этой статье мы разберём некоторые общие проблемы, связанные с этим, и оценим возможные способы реализации.
Согласование разных методов аутентификации
В современных приложениях нужно поддерживать несколько методов аутентификации. Пара почта-пароль постепенно уходит в прошлое — это недостаточно безопасно и удобно. Мы уже привыкли в каждом новом сервисе искать простые способы аутентификации через OAuth с помощью, например, Google или VK, что избавляет нас от необходимости хранить ещё один пароль. Выбор способов аутентификации зависит от того, какое конкретно у вас приложение; в сервисах для разработчиков, например, стоит делать приоритетным вход через Github, а в мобильных сервисах — через учётку Apple/Google/VK.
Поддержка разных способов входа открывает новую проблему: пользователь забывает, какой способ входа он использовал ранее. Не угадав, он получит чистый аккаунт, где, разумеется, не обнаружит своих данных, сохраненных в другом аккаунте. Перебирая способы авторизации, он будет создавать все новые и новые «мусорные» аккаунты, пока не найдёт профиль с нужными сохраненными данными. Если сервис используется редко, такие ошибки будут возникать чаще.
Решить эту проблему можно, например, с помощью синхронизации почтовых адресов. Если при OAuth-аутентификации в вашей базе обнаруживается совпадение по адресу, пользователю можно отправить сообщение типа «указанная почта уже использовалась при входе другим способом, проверьте свой способ аутентификации». Это потребует дополнительных ресурсов для реализации.
Существует альтернативное решение — «магические ссылки», которые отправляются на почту, указанную пользователем при входе. Перейдя по ссылке, он начинает авторизованную сессию. Такой метод аутентификации доказал свою надёжность и уже стал стандартом в web3-сервисах.
Slack поддерживает вход через «магические ссылки», достаточно ввести почту в нижнем поле.
Ещё одна опция входа, которая может понадобиться в проекте — это аутентификация через SSO, single sign-on. Она используется сотрудниками крупных компаний типа Microsoft или Google, которые заключают со сторонними сервисами соглашения о поддержке своих внутренних аккаунтов. Если вы делаете сервис для крупного бизнеса, без SSO едва ли удастся обойтись.
Защита от злоумышленников
Пересылать пароли между сервером и пользователем в виде обычного текста, очевидно, не стоит. Создавая собственную систему аутентификации, придется разобраться, как реализовано шифрование, как работает криптография. Хранить пароли и электронные адреса принято в хешированном виде, чтобы даже при утечке базы не возникло угрозы для пользователей и злоумышленники не могли соотнести логины и пароли.
Если вы как начинающий разработчик делаете все сами с нуля, то почти наверняка столкнетесь с проблемами; слишком много моментов, где что-нибудь может пойти не так. Вы должны держать в голове полную схему ее работы: и фронтенд (страницу авторизации), и серверную часть, и соединение с базой данных, и защиту, и много что еще. Не ограничили число попыток ввода пароля — получите взлом через брутфорс или даунтайм из-за DDoS-атаки. Другая опасность — это атаки через скрипты, через JS-код в ссылках. Если эта возможность не отключена, то злоумышленник с помощью ссылки может взять и перенаправить данные о сессии пользователя, токен сессии, в нужное место и легко зайти под его учетной записью в сервис.
В большинстве случаев разумно использовать уже готовые решения для аутентификации. Обычно они уже включают руководства с нужным бойлерплейт-кодом. Не стоит кривить нос и воспринимать это как запасной или крайний вариант относительно собственного решения. Даже no-code варианты могут в итоге быть более привлекательными, чем ваша собственная разработка, если вы не готовы положить на нее много-много ресурсов. Реализовать аутентификацию можно с помощью готовых библиотек или еще проще — в виде сервиса.
Библиотеки для аутентификации
Сегодня open source-библиотеки позволяют работать со всеми возможными OAuth-платформами и гибко настраивать взаимодействие в процессе аутентификации. Для максимальной кастомизации есть даже целые фреймворки. Почему здесь предпочтительны именно open source-решения? Так вы не становитесь критически зависимы от другого разработчика: даже если развитие open source-библиотеки прекратится, вы все равно сможете использовать ее последнюю актуальную версию, пока подыскиваете альтернативу.
Для Next.js, SvelteKit и SolidStart существует библиотека Auth.js, ранее известная как NextAuth.js. Она полностью берет на себя работу с сессиями со стороны сервера и клиента при авторизации через Google, Github и многих других OAuth-провайдеров. Весь процесс интеграции понятен, Auth.js ведет за руку, так что вам остается лишь задать необходимые настройки. А написав собственный адаптер для библиотеки, вы можете подключить здесь вообще любой OAuth-сервис.
Еще одна библиотека, на которую стоит обратить внимание — это Lucia. Она схожа с Auth.js, но имеет более широкую совместимость.
Auth as a Service — авторизация как сервис
Второй класс решений — авторизация как сервис. Вы полностью полагаетесь на стороннюю компанию и тратите минимум времени. Здесь наиболее известен сервис Firebase и его open-source альтернатива Supabase.
Когда пользователь авторизуется в вашем сервисе через Firebase, то все дальнейшее взаимодействие в сценарии происходит в рамках этой системы. Поэтому если вам потребуется какое-то более сложное взаимодействие, с участием альтернативной базы данных, то сценарии аутентификации придется разделить. Если у вас есть своя база данных, скажем, с постами пользователей, то нужно будет настроить ее соединение с базой Firebase. Или можно создать ее прямо в рамках Firebase; такая опция тоже доступна, ведь компания заинтересована в том, чтобы как можно глубже пустить корни в вашем сервисе.
Главный недостаток такого решения — это сложность в извлечении данных пользователя; по крайней мере в случае с Firebase это почти невозможно. Риск очевиден, ведь Firebase принадлежит Google, о чьей политике в отношении своих сервисов уже ходят легенды. «Да, Firebase приносит деньги, но вот вам три месяца и мы всё закрываем». Вполне реалистичный сценарий. Не стоит забывать и о возможной монетизации: хоть Firebase пока и бесплатна, едва ли это продлится долго.
Еще один вариант подобного решения — это Clerk; по сути, Firebase со встроенной Auth.js, функциональная и простая в интеграции платформа. Она предоставляет и необходимые конечные точки, и отправку данных о сессии. Clerk позволяет как хранить информацию о ваших пользователях в их системе, так и синхронизироваться с вашей базой.
Попробовать Clerk можно бесплатно в ограниченном режиме, на 5000 пользователей в месяц. С точки зрения экономики, Auth-as-a-Service подходит, скорее, не слишком массовым и при этом платным сервисам — в таком случае расходы на авторизацию не будут особо заметны. Если речь идет о сотнях тысяч пользователей, то авторизация в виде сервиса влетит в копеечку. Но когда вам нужно быстро прикрутить авторизацию, чтобы попробовать ваш сервис в деле, решения типа Clerk сэкономят время.
И все-таки свое или стороннее решение?
Когда у вас достаточно времени, есть смысл сделать собственную авторизацию, особенно если вы работаете на фул-стеке. Возможно, в рабочих проектах вы свою разработку и не примените, но зато досконально разберетесь, как что работает. Будет полезно, ведь авторизация — это важная часть любого сервиса.
Другой сценарий, когда уместно собственное решение — это требования повышенной безопасности к сервису, например, если вы работаете с биометрией радужной оболочки глаза или применяете обфускацию данных. Здесь полагаться на стороннее решение не стоит в принципе, все маломальские риски нужно избегать. Или применять лишь отдельные части стороннего решения — например, подходящие вам алгоритмы шифрования —, но не импортировать всё целиком.