Сегодня у меня жизнь в зуме, поэтому о магии коротко. Если очень хочется - дальше сам, ключевых слов уже достаточно ))
Поскольку мы добрались до условия с MITM, добавляем PKCE в стандартный Auth Code Flow, в [скобках] указаны стороны обмена, в угловых инфа, которая есть у <MITM>:
1. Неавторизованный клиент идёт в конечный сервис с простым запросом [клиент - бекенд] <нихрена>.
2. Бекенд сервиса генерит рандомный code, хеширует его (например, sha256 + base64url), генерит ссылку для редиректа на сервис авторизации, в которой передаётся хеш кода (code_challenge), метод хеширования (code_challenge_method) и URL возврата (return_url) редиректит клиента на авторизацию. [бекенд - клиент] <code_challenge, code_challenge_method>.
3. Клиент авторизуется в сервисе авторизации (auth), auth генерит одноразовый auth_code, сохраняет code_challenge и code_challenge_method, редиректит клиента на return_url, в который добавляет auth_code. [auth - клиент] <code_challenge, code_challenge_method, auth_code>
4. Бекенд обрабатывает return_url, получает auth_code, идёт на сервис авторизации за токеном, добавляя в запрос токена code_verifier (сам код, известный только бекенду, который ещё не передавался по сети) [бекенд - auth] <code_challenge, code_challenge_method, auth_code>.
5. Auth обрабатывает запрос, сверяет auth_code, выполняет code_challenge_method для code_verifier, сверяет результат с сохранённым code_challenge, если всё совпало, завершает flow (все коды с этого момента неактивны), отдаёт бекенду JWT access_token [auth - бекенд] <code_challenge, code_challenge_method, auth_code, уже бесполезные>.
6. Бекенд проверяет подпись в токене разворачивает токен, проверяет scope / роли, expire, пофиг что ещё там ему напихали в токен для его специфики, в signed-сессию клиента пишет хоть тупо user_id:1, отдаёт клиенту cookie с этой инфой и дальше полагается уже только на эту локальную инфу, никаких проверок токенов при каждом запросе нет. При желании периодически ходит с refresh_token в auth, узнаёт не отозвана ли там сессия. Или не ходит вообще, а при отзыве сессии ему прилетает коллбек от auth и он инвалидирует сессию у себя.
На всё это при достаточном уровне паранойи можно ещё навернуть привязки к IP, session state и много других весёлых штук, от которых обычно мутнеет голова при реализации.
И всегда остаётся вариант с применением паяльника к клиенту, это тупо дешевле.