Авторизация для ленивых. Наши грабли
Недавно мы решали задачу авторизации пользователей мобильного приложения на нашем бекенде. Ну и что, спросите вы, задача-то уже тысячу раз решённая. В этой статье я не буду рассказывать историю успеха. Лучше расскажу про те грабли, которые мы собрали.
Немного про проектМы в 2ГИС делаем крутой и точный справочник компаний. Для обеспечения качества и актуальности данных в 2ГИС есть несколько внутренних систем. Одна из них называется YouLa — нет, не та, где публикуют объявления. Наша YouLa поддерживает процесс выверки данных на местности.
Часть системы — мобильное приложение, с которым ходят пешеходники. Пешеходники — специалисты, которые обходят весь город. Карта разбита на разные участки для проверки.
Посмотрите, как выглядит территориальное деление Московской области. Разные цвета на карте обозначают разные назначения территорий.
Пешеходники приносят нам данные об организациях, в которые невозможно позвонить или у которых нет сайта. Например, шашлычные, киоски с овощами. Кроме того, бывает так, что у организации изменился телефон и мы не можем туда дозвониться. Во всех упомянутых случаях в организацию приходит наш специалист и сверяет информацию на местности.
Буквально несколько дней назад мы зарелизили новое мобильное приложение, для которого написали бекенд для синхронизации данных.
На нашем новом бекенде мы хотим знать, что за пользователь к нам пришёл.
К авторизации у нас несколько требований: — надежность и безопасность, — аутентификация по разным источникам, — аутентификация нескольких типов клиентов Web, Mobile, API.
Выбор способа аутентификации
Для реализации аутентификации существует много разных подходов, у каждого есть свои плюсы и минусы. Учитывая, что у нас большое количество точек интеграции, мы решили не изобретать велосипед и взять провайдера аутентификации и авторизации на базе OpenId Connect. Для авторизации на бекенде используем JWT.
Чем хорош JWT и стандарт OpenId Connect в Enterprise?Сейчас даже в рамках одной компании системы разрабатываются на разных стеках технологий и зачастую их потом тяжело подружить. В рамках одного стека технологий тоже можно поймать много странных и неожиданных эффектов, что уж говорить про ситуацию, когда у вас несколько систем. Для JWT и OpenId Connect список поддерживаемых клиентов и платформ впечатляет.
Схема работы всех компонентов выглядит вот так:
В рамках протокола поддерживается динамическое подключение поставщиков аутентификации. Мы рассматривали два источника — Google+ и ADFS. Но в дальнейшем нам бы хотелось просто и быстро расширять аудиторию продукта, например, за счёт подключения к системе других компаний, которые могли бы решать в нашей системе свои задачи.
С помощью JWT можно легко организовать аутентификацию разношёрстных клиентов. Более того, многие облачные клиенты предлагают сразу целый набор библиотек, облегчающих интеграцию провайдера в ваше приложение.
Облачные решения
Первой платформой, которую мы решили попробовать, был Auth0. Платформа очень крутая и для разработчика, и для администратора. В ней есть подробная документация, красивый и понятный Web UI для настройки всех параметров. В наше Java/Kotlin-приложение и на бекенд аутентификация была прикручена за пару часов.
Основные плюсы, которые мы отметили при работе с платформой Auth0: — подробная документация и бесконечное количество примеров кода на распространённых языках программирования; — возможность использовать для аутентификации не веб, а нативную форму входа.
Для того, чтобы реализовать поддержку JWT аутентификации в бекенде, достаточно написать всего несколько строчек (этот код для разных платформ будет отличаться только параметрами Authority и Audience), в некоторых случаях потребуется ещё указать сертификаты для проверки подписи токенов:
Для того, чтобы прикрутить аутентификацию к мобилке — ещё несколько строчек:
Как видно из примера, после аутентификации к нам приходит два токена + ещё один (RefreshToken) не показан в коде.
Для чего они нужны?
IdToken — содержит учетные данные пользователя AccessToken — для авторизации на API RefreshToken — для обновления AccessToken
Вопрос на засыпку: зачем необходимы два токена Access и Refresh?
Рассмотрим два случая кражи ключей:
- Негодяй украл только AccessToken. Тогда он будет валиден только до того момента, пока вы не воспользуетесь своим RefreshToken.
- Негодяй украл оба токена. Тогда, как только он воспользуется RefreshToken, ваши токены перестанут действовать и вас разлогинит из приложения. Если вы воспользуетесь своими учётными данными, то токены атакующего перестанут действовать. Использование двух токенов ограничивает время, на которое атакующий будет иметь доступ к вашим API.
Сам JWT-IdToken токен выглядит так:
Из этого токена мобильное приложение получает информацию об аутентифицированном пользователе. Соответственно, IdToken мы используем для отрисовки ФИО пользователя и его аватарки.
AccessToken мы прикрепляем к header запросов:
Для аутентификации web-клиента также достаточно просто выполнить интерактивный вход через IdenitityProvider. Ниже пример из официальной документации, как это прикрутить к Angular4-приложению.
Как видно из примеров, никто не ушёл обиженным — реализация для клиентов получается простой и понятной.
Для полного счастья нам не хватало аутентификации пользователей через нашу локальную Active Directory.
Для настройки синхронизации между Auth0 и локальной Active Directory, Auth0 предоставляет powershell-скрипт.
Когда мы уже обрадовались, что всё отлично работает, и пошли к админам с просьбой настроить синхронизацию между нашим AD и Auth0, то получили отказ. Ребята сказали, что максимум, куда они готовы лить наши данные, — это Azure. Также на решение повлияло то, что у нас уже использовалась подписка Office 365 и часть учёток уже была залита в Azure.
Окей, сказали мы.
Azure Active Directory B2CУ Microsoft есть сервис, который называется Azure Active Directory B2C. С помощью админов удалось настроить синхронизацию нашей AD с инстансом Azure AD и настроить вход через наш Active Directory Federation Services (ADFS).
Настройка политик входа в Azure B2CНа момент написания статьи сервис находится в превью версии, поэтому через UI можно настроить только самые примитивные сценарии, вроде входа через Google+ или Facebook. Вход через Active Directory производится через загрузку xml-файлов через Identity Experience Framework. На отладку сценариев входа ушло около восьми часов + ещё день на рефакторинг входа мобилки и прикручивание провайдера аутентификации от Microsoft. На бекенде потребовалось только указать новый IdentityProvider и Audience.
Для того, чтобы настроить вход, потребуется скачать репозиторий и пройти процедуру настройки, описанную в статье. Всего несколько часов вы программируете на xml — и вуаля! Ваш клиент аутентифицируется через серверы Azure.
Тут я привёл базовую политику входа. Для того, чтобы всё работало, требуется загрузить ещё три файла (они гораздо меньше, но вам всё равно придётся залезть в базовую политику).
Аутентификация Azure Active Directory B2C через ADFS выглядит следующим образом:
Всё было отлично. Мы запустили тестовую эксплуатацию приложения на наших пользователей, но столкнулись с несколькими блокирующими проблемами:
- Компонента аутентификации полностью не работал, если на устройстве не был установлен Google Chrome.
- Авторизация в Azure AD для новых пользователей перестала работать.
- После удачного логина через ADFS было невозможно разлогиниться. Для разлогина требовалось очищать кэш браузера Chrome на устройстве.
Вопрос с синхронизацией пользователей долго оставался подвешенным и, так как не было понятно, сколько времени потребует решение этой проблемы, мы экстренно переехали на другого провайдера. В этот раз мы попробовали Google Firebase Auth, так как он обладал нативным интерфейсом для входа и хорошо работал на всех наших тестовых устройствах. Так как на клиенте мы абстрагировались от конкретного провайдера, то переехали на Google Firebase Auth за час. Из доработок на бекенде потребовалось добавить код для скачивания сертификатов Google, которыми Firebase подписывает токены авторизации.
Сейчас, когда мы запустились в продакшн на Firebase, проблему с синхронизацией данных в Azure AD удалось решить. Поэтому в перспективе мы возможно вернёмся на Azure.
Обо всём ещё разок
Мы попробовали поработать с тремя разными провайдерами аутентификации в реальном проекте и, исходя из нашего опыта, я могу порекомендовать следующее.
Auth0 — отличный провайдер, очень дружественно настроенный к разработчикам. Приятное API, нет проблем с интеграцией. Если у вас нет каких-то административных барьеров, то рекомендую к использованию :)
Azure Active Directory B2C — хорош для Enterprise. Скорее всего, удастся договориться с админами и юристами. Пока ещё довольно сырой, поэтому приходится писать конфиги в xml. Ещё одна особенность платформы — для администрирования инстанса B2C в России требуется учётка того, кто привязал карту к аккаунту. Это неудобно, пока идёт отладка и тестирование.
Firebase Auth — самый лайтовый вариант. Подойдёт, если не требуется сложных сценариев входа и достаточно просто иметь аутентификацию. Из минусов — довольно аскетичная админ-панель и небольшой перечень дефолтных источников аутентификации.
Что ещё можно добавить?
Использование стандартов, в частности OpenId Connect, позволило нам: — быстро и дёшево адаптировать наше решение на клиенте, — сэкономить ресурсы на разработке серверной части, — не тратить время на дебаг и отладку кастомного кода аутентификации.