Нативный вход в десктопе (RFC 8252)

Когда десктопное приложение Hermes подключается к закрытому шлюзу (размещённой или self-hosted панели за OAuth-провайдером), войти оно может двумя путями:

  1. Нативный вход (RFC 8252) — приложение открывает ваш настоящий системный браузер, вы подтверждаете вход там, где привыкли, а приложение получает токены и хранит их в виде файлов, доступных только владельцу, в своём каталоге пользовательских данных (по желанию — с шифрованием через связку ключей ОС, Настройки → Шлюз). Ни встроенного webview, ни сессионных cookie. Так работает по умолчанию везде, где шлюз это поддерживает.
  2. Встроенный вход (устаревший запасной вариант) — приложение открывает маленькое окно браузера внутри себя и забирает сессионный cookie шлюза. Включается автоматически, если шлюз старой сборки и о нативном входе не сообщает.

Выбирать между ними вручную не нужно: приложение само определяет, что умеет шлюз, и берёт лучший вариант. Ниже — что при этом происходит и почему.

Зачем нативный вход

У встроенного браузера внутри нативного приложения хорошо известные минусы. Страница входа не видит вашу текущую браузерную сессию, поэтому логин и многофакторка проходятся заново; менеджеры паролей и passkey часто отказываются работать; а само приложение вынуждено выковыривать сессионный cookie из приватного webview. RFC 8252 («OAuth 2.0 for Native Apps») — принятая в индустрии практика, которая всё это снимает: авторизацию проводим в системном браузере, а приложению отдаём его собственные токены.

Конкретно для Hermes нативный вход означает:

  • Нет встроенного webview. Авторизация идёт в Safari, Chrome, Firefox или Edge — в том, чем вы пользуетесь, — со всеми вашими логинами, расширениями и passkey.
  • Нет сессионных cookie. Приложение держит токен доступа OAuth (короткоживущий) и токен обновления в виде файлов, доступных только владельцу, — с шифрованием при хранении через связку ключей ОС (safeStorage в Electron), если включён опциональный переключатель связки ключей в Настройки → Шлюз. REST-вызовы и билеты WebSocket аутентифицируются заголовком Authorization: Bearer, а не банкой с cookie.

Как это работает

Desktop app                Gateway (/auth/native/*)          Nous Portal (IDP)
   │ 1. open loopback 127.0.0.1:<random port>
   │ 2. system browser ─►  /auth/native/authorize
   │    (PKCE challenge)    (starts the normal PKCE login) ─► /oauth/authorize
   │                        ◄──── code ──── /auth/callback ◄──┘
   │                        3. mint one-time gateway code
   │ ◄─ 302 127.0.0.1/cb?code=… ─┘
   │ 4. POST /auth/native/token (code + PKCE verifier)
   │ ◄─ 5. { access_token, refresh_token, expires_at } ───────┘
   │ 6. store in local token store; use Bearer for REST + WS tickets

Шлюз выступает посредником: для десктопного приложения он сервер авторизации, а для вышестоящего провайдера идентичности (Nous Portal) — OAuth-клиент. Иначе нельзя: client_id и разрешённые адреса перенаправления привязаны к домену самого шлюза, и десктопное приложение не может быть прямым клиентом Портала. При этом десктоп получает полноценный опыт RFC 8252 — собственную пару PKCE, собственное перенаправление на loopback и токены, которыми владеет он сам.

PKCE (RFC 7636) защищает переход через loopback: одноразовый код шлюза бесполезен без верификатора, а тот не покидает приложение. Код одноразовый и живёт недолго.

Определение возможностей и запасной путь

Десктоп читает публичный эндпоинт шлюза /api/status, где объявлен массив auth_flows:

Значение auth_flowsЧто означает
["cookie", "native_pkce"]Шлюз умеет нативный вход → приложение им и пользуется
["cookie"]Шлюз умеет только устаревший поток → приложение открывает встроенный webview
(поля нет)Шлюз старой сборки → приложение открывает встроенный webview

Если нативный вход объявлен, но сорвался по локальной причине — скажем, средство защиты заблокировало слушателя на loopback или вы закрыли вкладку браузера, — приложение само откатится на встроенный поток, чтобы вы всё-таки вошли.

Жизненный цикл токенов

  • Токен доступа: короткоживущий, счёт на минуты. Уходит заголовком Authorization: Bearer в каждом REST-вызове и при выпуске билета WebSocket.
  • Токен обновления: живёт дольше, ротируется. Когда токен доступа близок к истечению, приложение вызывает /auth/native/refresh и обновляет обе части, после чего обновляет своё хранилище токенов.
  • Окончательное истечение: токен обновления мёртв — просрочен, отозван или замечено повторное использование — приложение стирает сохранённые токены и просит войти заново.
  • Выход: чистит и сохранённые нативные токены, и старый сессионный cookie этого шлюза, если он был.

Тем, кто держит шлюз

Нативный вход появляется сам на любом закрытом шлюзе, где зарегистрирован провайдер интерактивных сессий. Настраивать ничего не нужно: маршруты /auth/native/* и объявление auth_flows входят в подсистему авторизации панели. OAuth-провайдеры (например, встроенный провайдер Nous) выступают посредником при редиректе на внешний IDP; парольные провайдеры (например, встроенный плагин basic-auth) вместо этого открывают в системном браузере форму входа шлюза /login — именно это позволяет менеджерам паролей ОС (macOS «Пароли» и т. п.) автоматически подставлять данные в форму, чего не может дать ни один встроенный webview десктопного приложения. Провайдеры, работающие только по токену (например, drain), — это не интерактивный вход, и native_pkce они не объявляют.

Задействованные эндпоинты (все публичные, доавторизационные, как и существующие OAuth-маршруты /auth/*):

  • GET /auth/native/authorize — старт посреднического входа по PKCE
  • POST /auth/native/token — обмен loopback-кода и верификатора на токены
  • POST /auth/native/refresh — ротация токенов по токену обновления приложения

Смотрите также

перевод и редактура:

обновлено

⌕ ESC