Нативный вход в десктопе (RFC 8252)
Когда десктопное приложение Hermes подключается к закрытому шлюзу (размещённой или self-hosted панели за OAuth-провайдером), войти оно может двумя путями:
- Нативный вход (RFC 8252) — приложение открывает ваш настоящий системный браузер, вы подтверждаете вход там, где привыкли, а приложение получает токены и хранит их в виде файлов, доступных только владельцу, в своём каталоге пользовательских данных (по желанию — с шифрованием через связку ключей ОС, Настройки → Шлюз). Ни встроенного webview, ни сессионных cookie. Так работает по умолчанию везде, где шлюз это поддерживает.
- Встроенный вход (устаревший запасной вариант) — приложение открывает маленькое окно браузера внутри себя и забирает сессионный 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— старт посреднического входа по PKCEPOST /auth/native/token— обмен loopback-кода и верификатора на токеныPOST /auth/native/refresh— ротация токенов по токену обновления приложения
Смотрите также
- OAuth через SSH и на удалённых хостах — тот же приём с колбэком на loopback для OAuth провайдеров и MCP на удалённых машинах.
- Запуск Hermes с Nous Portal