OAuth 2.0, OpenID Connect и SSO для самых маленьких
Всем привет! Меня зовут Павел, я Head of Development в Банки.ру. Сегодня хочу погрузиться с вами в, кажется, уже давно заезженные темы: Single sign-on, OAuth, OpenID и нюансы их реализации.
Здесь, на Хабре, достаточно гайдов с примерами реализаций на разных языках или же, наоборот, более академического и теоретического материала. Но, на мой взгляд, не хватает комплексного подхода, который бы охватил в одном цикле статей стандарты, спецификации, практические советы, особенности данных протоколов и технологий, а также их связей между собой.
Я решил восполнить этот пробел и подготовил именно такой материал.
В этой статье погрузимся в теоретическую часть и рассмотрим:
основные Flow OAuth 2.0 и отдельно Authorization Code Flow with Proof Key for Code Exchange
OpenID Connect (OIDC)
Single Sign-On или SSO: схему реализации и применение SSO в мобильных и веб-приложениях
Разбираемся с терминологией
Для начала давайте рассмотрим общие термины, которые будут встречаться в материале. Думаю, для большинства читателей этот абзац будет скорее повторением уже хорошо известных определений, поэтому, если уверены в своих знаниях, смело пропускайте знакомые части.
JWT (JSON Web Token) — открытый стандарт, описанный в RFC 7519. Определяет компактный способ для передачи информации между сторонами в виде JSON-объекта. Токены создаются сервером, подписываются секретным ключом и передаются клиенту. Клиент же в дальнейшем может проверить подлинность данного токена, использовать его для подтверждения подлинности, извлечь из него полезную нагрузку и т.п. Рассматривая клиент-серверное взаимодействие, обычно выделяют два типа токенов: access и refresh.
Простыми словами, access token — это ключ доступа к защищенному ресурсу, обычно с коротким сроком жизни, а refresh token позволяет запросить новый access и имеет длинный ttl.
Большое преимущество JWT токена в том, что его можно валидировать на стороне клиента с помощью публичного ключа без передачи на выпустивший его сервер, а хранить информацию об активных сессиях на стороне клиентского приложения не нужно. К тому же, сам токен достаточно небольшого размера, что позволяет передавать его как в теле Post запроса и заголовках, так и в URI query параметрах. Второе, конечно, менее безопасно, но зато позволяет реализовать дополнительные flow авторизации.
OAuth 2.0 — как упомянуто в самом стандарте, это фреймворк/протокол авторизации, который описывает, каким образом реализовывать взаимодействие между сервисами для обеспечения стороннему приложению безопасной авторизации и ограничения доступа к ресурсам. Рассмотрим роли, определенные в OAuth 2.0:
Resource owner — сущность, которая способна предоставить доступ к защищенному ресурсу. Это может быть конечный пользователь (end-user) или система. В контексте данного материала под Resource owner мы всегда будем подразумевать реального пользователя.
Resource server — сервер с защищенным ресурсом, который способен предоставить к данному ресурсу доступ посредством access tokens.
Client — приложение, которое запрашивает доступ к защищенному ресурсу от имени resource owner и с его разрешения. Можно выделить два типа клиентов: полноценный backend сервис (Confidential) и клиентские приложения (Public). Подробнее каждый тип, его особенности и возможности по хранению данных описаны в RFC-6749.
Если коротко:
web-application client — confidential клиент. Например, backend web-приложение, которое взаимодействует Resource owner с помощью user-agent-а (web-браузера и т.д.);
user-agent-based application — public клиент, весь код которого выполняется на пользовательском user-agent-е. SPA — отличный пример такого типа клиента;
native application — также public клиент. Нативное приложение, которое устанавливается на устройство пользователя. Например, любое нативное приложение, установленное к вам на телефон.
В контексте дальнейшего рассмотрения реализации SSO и сквозных сессий между веб- и нативными приложениями сразу уточним различия между двумя типами user-агента:
external user-agent или внешний UA — это UA, который в нашем случае способен обработать запросы на авторизацию от пользователя. Он является защищенной отдельной сущностью по отношению к целевому нативному приложению. Главное — у приложения нет доступа ни к сессионному хранилищу, ни к контенту страницы, отображенной через данный типа UA. Другими словами, это нативный браузер операционной системы.
embedded user-agent или встроенный UA — это UA, который, как это ни странно, встроен в нативное приложение. У приложения есть полный доступ к к сессионному хранилищу и контенту страницы. Отличный пример — всем известная реализация WebView в нативных приложениях.
OAuth 2.0: основные Flow
Возвращаемся к OAuth 2.0. Всего в соответствующем RFC-6749 подробно описано 4 flow. Коротко пройдемся по ним:
Authorization code — этот flow основан на редиректах. Клиент должен уметь взаимодействовать с user-agent-ом (обычно браузером) и обеспечивать клиент-серверное взаимодействие. Следовательно, этот flow подходит только для confidencial клиентов. Его мы рассмотрим подробнее чуть позже.
Client credentials — этот flow подходит только для Machine-to-Machine (M2M) приложениям (кроны, CLis, backend сервисы). Напоминает «обычную» авторизацию, которая выполняется на основе client_id и client_secret: по сути, логин и пароль, в случае пользователя. Клиент выполняет запрос на сервер авторизации с client_id и client_secret и в ответ получает access token для доступа к запрашиваемому ресурсу.
Implicit — первая попытка «адаптировать» Authorization code flow для public-клиентов, которые умеют работать с redirection URI (например, SPA или мобильные приложения). Основное отличие в том, что после успешной авторизации resource owner вместо получения authorization-code и его обмена на access token, сервер авторизации сразу возвращает access token в query в redirect URI. Сейчас основная рекомендация стандарта — использовать Authorization Code Flow with PKCE. Он более безопасен: не передает токен в открытом виде, а еще позволяет возвращать и использовать клиенту refresh токен.
Resource owner password credentials. Flow считается deprecated и не рекомендуется к использованию, так как обладает проблемами с безопасностью. Resource owner-у необходимо передавать логин и пароль через форму, расположенную на клиенте, что изначально дает возможность использовать его только для полностью доверенных клиентов.
Device authorization — бонусный пятый flow. Хоть ранее я и отмечал, что в OAuth 2.0 всего четыре flow, этот был добавлен позже в RFC 8628 в 2019 году. В нем описывается flow, придуманный для устройств, на которых тяжело вводить логин и пароль (телевизоры/приставки/что-то без графического интерфейса). Устройство отображает user code и verification URI/QR-код resource-owner-у (владельцу устройства) и начинает опрашивать сервер авторизации о завершении подтверждения. Resource-owner c другого устройства переходит по ссылке и подтверждает выдачу необходимых прав.
Примечательно, что публикация OAuth 2.0 (RFC 6749) была в далеком 2012 году, и уже давно идет работа над обновленной версией стандарта OAuth 2.1. В новой версии совершена попытка объединить в себе несколько уже опубликованных спецификаций OAuth 2.0 for Native Apps (RFC 8252), Proof Key for Code Exchange (RFC 7636), OAuth for Browser-Based Apps, OAuth 2.0 Security Best Current Practice и удалить deprecated фичи. Забавно, что завершающая дата черновика постоянно переносится. Сейчас стоит 16 ноября 2024 года (!), но уже очевидно, что дата снова будет перенесена.
Основные изменения грядущей версии:
PKCE flow стал обязательным для всех клиентов, которые используют authorization code flow
Resource Owner Password Credential и The Implicit grant (response_type=token) больше не является частью спецификации
Запрещение передачи токенов в query URI
Более жесткие ограничения к refresh токенам
С путем развития протокола OAuth 2.0 и всем многообразии RFC можно ознакомиться в статье «It’s Time for OAuth 2.1».
Принцип работы Authorization Code Flow with Proof Key for Code Exchange
Как я упоминал ранее, Authorization Code Flow with PKCE основан на обычном Authorization Code Flow и является практически единственным выбором при реализации авторизации для public клиентов (SPA и нативные мобильные приложения). Традиционно flow хорошо и подробно описан в соответствующем RFC OAuth 2.0 RFC 7636. Мы же постараемся разобрать основные отличия от Authorization Code Flow.
Данный флоу расширяет Authorization Flow с помощью секрета Code Verifier, который создается целевым приложением. Основная суть: если злоумышленник перехватит Authorization Code, он не сможет обменять его на токены без соответствующего секрета Code Verifier.
Алгоритм работы flow следующий:
Происходит пользовательский интент на авторизацию. Например, нажатие на кнопку Login.
Целевое приложение генерирует случайный code_verifier и из него code_challenge. Сохраняет их в приложении (подробнее в RFC-7636).
Приложение делает редирект в браузер на эндпоинт сервера авторизации вместе со сгенерированным ранее code_challenge.
Сервер авторизации также запоминает code_challenge для данной сессии. Соль в том, что code_verifier остается только внутри приложения и не передается по сети.
Пользователь вводит логин и пароль на странице сервера авторизации. При необходимости определяет список разрешений, которые будут выданы целевому приложению.
Сервер авторизации делает обратный редирект на клиент и возвращает код авторизации. При этом code_challenge обратно не возвращается.
Приложение обменивает код на токен (-ы) практически точно так же, как при Authorization Flow, но дополнительно передает code_verifier, который был сохранен в пункте 2
Сервер авторизации вычисляет code_challenge на основе данных пайлоада запроса на получение токенов и сравнивает с code_challenge, который был отправлен в пункте 3. Если значения совпадают, возвращает набор токенов для доступа к защищенным ресурсам.
Авторизация VS Аутентификация
Рассмотрев принципы работы OAuth 2.0, гораздо проще вернуться к базовым вопросам и перейти к подробным определениям аутентификации (authentication), авторизации (Authorization) и их различий, а после — к OpenID и SSO.
Несмотря на кажущуюся интуитивность понятий «авторизация» и «аутентификация», эти термины вызывают много путаницы и находятся в «серой зоне».
Авторизация — это предоставление доступа к данным и функциям от одного приложения другому. То есть, по сути, это выдача прав на определенные действия внешнему приложению от лица пользователя без необходимости знать личность пользователя. Мне нравится пример в цикле статей о спецификации OAuth 2.0 от Aaron Parecki c отелем и карточкой доступа:
«Представим, вы приехали в долгожданный отпуск, зарегистрировались в отеле и получили карточку от своего номера. С помощью данной карты в течение всего отпуска вы будете получать доступ к услугам отеля и к своему номеру. Но при этом карта ничего не говорит лично о вас: каким образом вы были зарегистрированы, ваши паспортные данные — только о самом факте доступа к определенным услугам и сервисам на время вашего проживания. В данном контексте можно рассматривать карту как access-token в OAuth 2.0.».
Подводя итог, OAuth 2.0 не обеспечивает механизм определения того, каким образом пользователь был аутентифицирован, только указывает факт того, что пользователь делегировал целевому приложению действовать от его имени без необходимости идентифицировать самого пользователя.
Аутентификация — это процесс «выявления/определения» пользователя, то есть подтверждения, что конкретный пользователь является тем, за кого себя выдает.
Именно для этого и был создан OpenID Connect.
OpenID Connect (OIDC) простыми словами
OpenID Connect (OIDC) — это тонкий слой поверх OAuth 2.0. Протокол добавляет сведения о логине и профиле пользователя, который вошел в учетную запись. Помимо этого, позволяет реализовать Single Sign-On (SSO) и тем самым дает возможность входить в несколько приложений/несколько веб-сайтов с использованием единовременной аутентификации пользователя. Например, сервис посредством OpenID может определить, что зашедший пользователь — именно Иванов Иван Иваныч, определенного identity provider.
Другими словами (заглянул в Wikipedia):
OpenID — открытый стандарт децентрализованной системы аутентификации, предоставляющей пользователю возможность создать единую учетную запись для аутентификации на множестве не связанных друг с другом интернет-ресурсов, используя услуги третьих лиц. Информацию о пользователе, вошедшем в систему (о Resource Owner’е), часто называют личными данными [identity], а Authorization Server в контексте OIDC — поставщиком личных данных IDP.
В основе OpenID Connect лежит концепция «ID Tokens». Как раз таки в ней и возвращается аутентификационная информация о пользователе. При запросе токенов клиент вместе с access и refresh токенами получает также и ID-token.
Давайте рассмотрим на примере API google, успешный ответ которого будет содержать набор следующих полей:
В сигнатуре ID-токена может быть много идентификационной информации (claims). С полным набором полей можно, как обычно, ознакомиться в спецификации OpenID.
Я хочу остановиться на поле «aud»: «my_client_id», которое отвечает за принадлежность полученного токена к клиенту.
Еще один наиболее важный момент, о котором я забыл упомянуть ранее — это безопасность. Клиенту желательно (а лучше обязательно) провалидировать все токены и проверить, что данный набор действительно был издан ожидаемым паблишером, не был подменен или выпущен для другого клиента.
Что такое SSO или Single Sign-On
Технология единого входа (Single Sign-On) — технология, которая позволяет пользователям безопасно аутентифицироваться сразу в нескольких приложениях и сайтах, используя один набор учетных данных, и бесшовно переходить между ними без повторной аутентификации.
Аутентификация через SSO начинается, когда пользователь входит в систему через один из клиентов, связанных с SSO. Вместо того, чтобы каждый раз вводить имя пользователя и пароль для каждого сервиса, пользователь делает это один раз. Данные используются для создания уникальной сессии, которая будет признана другими сервисами.
Когда пользователь пытается получить доступ к другому сервису или приложению в рамках SSO, сервис запрашивает у IdP подтверждение аутентификации пользователя. Вместо повторного запроса учетных данных сервис принимает токен, который доказывает, что пользователь уже прошел аутентификацию.
В целом, SSO является частью более общей концепции Identity and Access Management (IAM) — комплексной системе, охватывающей процессы, которые используются для управления идентификацией пользователей и их доступом к различным ресурсам.
Все аспекты IAM-системы можно разделить на три группы: пользовательский опыт, безопасность и инфраструктура:
На каждом аспекте подробно останавливаться не буду, но поделюсь с вами ссылкой для поверхностного ознакомления.
Реализация SSO основывается на различных протоколах, таких как OAuth, OpenID Connect и SAML (Security Assertion Markup Language). SAML также выходит за рамки данного материала, т.к. данный стандарт все-таки больше используется в корпоративных SSO решениях. Поэтому остановимся только на определении для понимания различий.
SAML — это стандарт, основанный на XML, для обмена аутентификационными и авторизационными данными между двумя сторонами: провайдером идентификации и поставщиком услуг.
Схема реализации SSO
С протоколами OAUth и OpenID мы познакомились с вами ранее, поэтому давайте сразу схематично рассмотрим один из примеров реализации SSO на практике. Так как мы хотим получить сквозную аутентификацию как в веб-приложениях, так и на мобильных клиентах, в основу мы по умолчанию закладываем auth code flow with PKCE и получаем примерно следующую картину: где для начала каждый клиент нужно интегрировать с IdP
Схематично алгоритм работы SSO между сервисами можно описать следующим образом:
Схема алгоритма работы SSO
Пользователь хочет авторизоваться на одном из хостов, использующих SSO
Происходит стандартный путь OAuth 2.0, при котором пользователь будет перенаправлен на форму авторизации на хост IdP (Identity Provider), где пользователь вводит свои данные для входа
Далее происходит редирект на целевой клиент, где, согласно стандарту, сервис обменивает полученный код на набор токенов. При этом перед редиректом IdP также сохраняет сессию пользователя на своем домене (например, в куках)
Клиент проверяет полученный набор, аутентифицирует пользователя. При его отсутствии создает новую учетную запись и также авторизует нового пользователя
Пользователь хочет авторизоваться на другом сервисе, который также использует данный IdP
Клиент перенаправляет пользователя на страницу авторизации на IdP, но только в этот раз провайдер сразу же «подцепит» предыдущую сессию, т.к. она уже есть на хосте IdP с момента предыдущей авторизации в пользовательском UA (браузере)
Далее пользователь будет перенаправлен обратно на клиента с кодом, согласно PKCE flow, без необходимости ввода логина/пароля
Пользователь будет авторизован так же, как на клиенте 1
Вот и все. SSO на минималках готова. Конечно, при условии, что у вас, например, микросервисная архитектура и часть сервисов расположена на одном хосте, можно вынести часть авторизации и обмена code на набор токенов в отдельный сервис, а в остальных оставить только рефреш.
Схематично можно представить подобную схему так:
В дополнение: если присмотреться к схеме аутентификации, представленной чуть ранее (Рисунок «Схема алгоритма работы SSO»), можно увидеть, что пользователь сам инициирует намерение авторизоваться на клиенте-2 (например, нажимает кнопку «вход»). Но часто хочется получить другой путь, при котором, если пользователь авторизовался через IdP на одном клиенте, можно сразу авторизовывать его на всех остальных сервисах без того, чтобы пользователь нажимал на кнопку «вход».
Стандарт, как обычно, подумал за нас и описал возможные реализации подобного поведения.
Собственно, есть 2 варианта:
Встраивание iframe, что наиболее актуально для SPA-приложений. Традиционно оставляю ссылку, где подробно описана схема работы Silent Refresh Token in iframe Flow.
При варианте с редиректом принцип работы такой же, как мы рассматривали при реализации SSO между двумя web-клиентами. Отличие: при передаче параметра prompt=none, если сессии на IdP нет, то редиректа на форму авторизации не будет. Так что пользователя сразу же вернет на backurl с соответствующими параметрами. Без такого поведения не было бы возможности реализовать silent подхват сессии для публичных роутов, которые могут быть доступны как авторизованным, так и неавторизованным пользователям.
Ну и, конечно, пример того, как на практике может работать SSO, прилагается:
SSO и мобильные и веб-приложения
Как я уже упоминал ранее, технологию единого входа SSO можно использовать не только для сквозной аутентификации между различными web-сервисами, но и в связке с нативными мобильными приложениями. На первый взгляд это может показаться золотой пулей: что может быть лучше, чем сквозная авторизация между сайтом и приложением?
Сайт → IdP > авторизованный пользователь → приложение → «практически» бесшовная авторизация в приложении
Приложение → IdP → авторизованный пользователь → переход на сайт как авторизованный пользователь
Однако ограничения у подхода тоже есть.
Для достаточно взрослых компаний это достаточно стандартный кейс:
Есть популярный сайт с большим количеством трафика и пользователями
Появляется потребность попробовать новые каналы и создать нативные приложения на базе web
Исторически весь новый функционал изначально появляется на web, а следовательно, сайт всегда впереди по функциональности в сравнении с приложением
В силу ограниченности ресурсов часть функционала приложения открывается в webview или редиректом на web-сайт
Однако ограничения у подхода тоже есть.
Для простоты будем считать webview вкладкой в режиме инкогнито с дополнительными возможностями: засеттить любые заголовки, перехватить редиректы и т.п. Предположим, у вас реализована стандартная OAuth 2.0 авторизация. Тогда при открытии webview вы легко можете передать токен в заголовке авторизации. Например, так: AUTHORIZATION: Bearer [token]
А дальше уже точно так же обработать и провалидировать токен на веб-сервисе, до закрытия webview контекст у вас сохранится. Для пользователя путь будет бесшовным, насколько это возможно. На всякий случай уточню, что здесь речь не идет про аутентификацию пользователя: подразумевается, что пользователь уже авторизован в приложении, нужно только «пробросить» токены в Embedded User Agent.
Но как быть, если в приложении нужно открывать переходы по ссылкам в UA нативного браузера? Можно, конечно, придумать различного рода воркэраунды, но обычно они не сильно безопасны (передачу токена напрямую в query параметров не предлагать) и требуют костылей в реализации.
Как вы уже догадались, это идеальный кейс для реализации с помощью SSO. Бонусом будет и сквозная авторизация с дружественными доменами, которые также используют данный IpD.
В целом есть отличная RFC OAuth 2.0 for Native Apps, в котором вы сможете подробнее ознакомиться с реализацией для мобильных приложений.
Начну с ответа на очевидный вопрос «А давайте сделаем открытие формы webview?»
Ответ — нет, не давайте. Во-первых, как говорит стандарт, это небезопасно, так как в приложении у нас есть доступ ко всей внутрянке webview. Во-вторых, даже если вы являетесь владельцами IdP и нативного приложения, и их взаимодействие между собой можно считать доверенными, то сквозной авторизации между внешним браузером и приложением в любом случае не будет: сессия WebView и нативным браузером устройства несквозная.
В общем виде сама практическая реализация является ничем иным, как реализацией PKCE flow:
Интент пользователя на авторизацию в нативном приложении
Приложение открывает дефолтный браузер, ну или ChromeCustomTabs, SafariVC со страницей формы авторизации
Реализация стандартной части PKCE flow до момента обмена клиентом кода на access и refresh токены
Перехват приложением редиректа с кодом в приложении
Вызов нативным приложением эндпоинта обмена кода на токены авторизации
Валидация полученных данных и кода. Возврат токенов мобильному приложению
Для перехвата редиректа в приложении можно использовать любой backurl как со стандартной https схемой, так и кастомной, что очень полезно для реализации на iOS. В настройках того же Keycloak легко задаются разрешения и ограничения на схемы для обеспечения безопасности. Также, насколько я помню, Chrome выпустил обновление, которое запрещает без пользовательского намерения (клик по ссылке и т.п.), переходить по URL с кастомной схемой.
Дополнительно на некоторых устройствах могут быть проблемы с перехватом обратного редиректа, поэтому нелишним будет сделать дополнительную финальную веб-страницу с кнопкой возврата в приложение, на которую браузер редиректит после успешной авторизации. Веб-страница автоматически пытается сделать редирект уже внутрь приложения. Если этого не происходит, то пользователь может сам нажать на кнопку на странице явно и вернуться в приложение. Выглядит базовая реализация примерно так:
Следует признать, что это, конечно, не полностью нативный путь авторизации, а значит воронка авторизаций на реальных пользователях будет все равно чуть ниже. Но есть огромный плюс — получаем полностью сквозную сессию как в контуре нативного приложения, так и на целевом домене. Раньше дела обстояли еще хуже: обязательным условием был переход полностью в нативный браузер вне контура приложения и ввод пользовательских данных в форму там. Но, к нашему счастью, появились поддержка in-app browser tabs и его аналогов, что позволяет открывать страницу авторизации, как на примере выше.
Apple прошел долгий и тернистый путь реализации SSO на платформе и предоставляет несколько вариантов для реализации: SFSafariViewController, SFAuthenticationSession, SWebAuthenticationSession. Есть отличная статья A history of the Mobile SSO, подробно вскрывающая данную историю и особенности реализации.
Но остановимся на главном:
Apple пытается быть максимально информативным для пользователя, в частности, при работе с приватностью и трекингом пользователя. Поэтому с iOS 11 всегда показывает явный promt, требующий согласия пользователем на авторизацию через целевой домен:
В iOS 11 было и еще одно важное изменение, а именно: сессия перестала быть сквозной с браузером. Следовательно, сам смысл SSO на тот момент полностью пропал. Впоследствии Apple, конечно, вернул разработчикам данную возможность, но, думаю, данный prompt уже с нами навсегда
В теории, если вы хотите сразу же авторизовать пользователя не только на домене с сервером авторизации, но и на еще одном дружественном, то можно было бы через дополнительный редирект реализовать схему, в которой пользователь сначала авторизуется на дружественном домене, а потом вы перенаправляете юзера обратно на IdP с backurl, который будет перехвачен уже приложением. То есть схема работы имела бы следующий вид:
На платформе Android такой трюк прекрасно работает, сессия остается активной на всех доменах. А вот на Apple данный подход не сработает снова из-за политики безопасности, так как в promt пользователь дает согласие только на 1 домен, для всех остальных сессия так же остается несквозной. Получается следующая картина: пользователь авторизован на дружественном домене и в приложении, но не авторизован на домене SSO, что для нас недопустимо.
Ну и наконец, стоит упомянуть выход из приложения. Без реализации SSO можно было бы просто удалить сессию и дальше на 401 редиректить пользователя на нативную форму авторизации. В нашем же случае, как несложно догадаться, вы получите вот такой эффект:
Так как сессия на домене SSO до сих пор актуальная и в браузере мы ничего не очищали, авторизация автоматически подхватывается с IdP, и пользователя снова авторизует в приложении под текущим логином. Единственный способ исправить данную фичу/баг — выполнять выход через открытие браузера и реализацию выхода на целевом домене SSO.
На этом все!
Надеюсь, материал помог вам узнать что-то новое или хотя бы уложить уже имеющиеся знания по полочкам. Призываю вас всегда помнить, что спецификации и стандарты, какими бы неудобными и неоптимальными они ни казались, написаны профессионалами, которые потратили огромное количество времени и сил на поиск оптимальных решений и закрытие дыр в безопасности. Не кидайтесь в реализацию сразу же, потратьте время на изучение документации и предметной части. И (повторюсь еще раз) любите стандарты!
На этом первая, теоретическая, часть закончена. В будущем раскрою подводные камни, с которыми наша команда столкнулась при реализации сквозной авторизации на куче разнообразных сервисов и технологий, с учетом А/Б тестирования и бесшовного переключения серверов авторизации, настройки Keycloak-a и интеграции с ЕСИА и, конечно же, все это без разлогина ранее авторизованных пользователей.
Спасибо за ваше внимание!