デスクトップアプリのネイティブサインイン(RFC 8252)
Hermes のデスクトップアプリが認証付きゲートウェイ(OAuth プロバイダの後ろに置かれた、 ホスト型または自前で立てたダッシュボード)につなぐとき、サインインの方法は 2 つあります。
- ネイティブサインイン(RFC 8252) — アプリが普段使っているシステムのブラウザを開き、
すでに信頼しているそのブラウザ上で承認します。アプリはトークンを受け取り、自分のユーザー データ用ディレクトリに、本人だけが読めるファイルとして保存します(設定 → Gateway で、OS の キーチェーンによる暗号化も選べます)。埋め込みの WebView も、ブラウザのセッションクッキーも 使いません。 ゲートウェイが対応していれば、こちらが既定です。
- 埋め込みサインイン(従来の代替手段) — アプリが小さなアプリ内ブラウザの窓を開き、
ゲートウェイのセッションクッキーを受け取ります。ゲートウェイが古い版で、ネイティブ サインインに対応していると表明しない場合に自動で使われます。
どちらを使うかを選ぶ必要はありません。アプリがゲートウェイの対応状況を調べて、良いほうを 選びます。このページでは、そのとき何が起きているのか、なぜそうしているのかを説明します。
ネイティブサインインを選ぶ理由
OAuth のためにネイティブアプリの中へブラウザを埋め込むやり方には、よく知られた弱点が あります。ログイン画面が既存のブラウザのセッションを見られないので、認証情報を打ち直し、 多要素認証もやり直しになります。パスワード管理ソフトやパスキーが動かないことも多く、 さらにアプリは非公開の WebView からセッションクッキーを読み出すことに頼っています。RFC 8252("OAuth 2.0 for Native Apps")は、こうした問題をまとめて避けるための業界の定石です。 やることは一言でいえば、認可はシステムのブラウザで行い、アプリには自分のトークンを 渡すということです。
Hermes に即していうと、ネイティブサインインは次のことを意味します。
- 埋め込みの WebView を使いません。 認可は Safari、Chrome、Firefox、Edge など、普段
使っているブラウザで行われます。ログイン状態も、拡張機能も、パスキーもそのまま使えます。
- セッションクッキーを使いません。 アプリが持つのは OAuth のアクセストークン(短命)と
リフレッシュトークンで、本人だけが読めるファイルとして保存されます。設定 → Gateway の キーチェーンの切り替えを有効にすれば、OS のキーチェーン(Electron の safeStorage)で 保存時に暗号化されます。REST の呼び出しと WebSocket のチケットは、クッキーではなく Authorization: Bearer ヘッダーで認証します。
動きの流れ
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ゲートウェイはこの流れを仲立ちします。すなわち、*デスクトップアプリから見れば*認可サーバー であり、*上流の ID プロバイダ*(Nous Portal)から見れば OAuth のクライアントです。これは、 上流の client_id と許可されたリダイレクト先がゲートウェイ自身のオリジンに結びついていて、 デスクトップアプリが Portal の直接のクライアントになれないためです。それでもデスクトップ側は RFC 8252 の恩恵をそのまま受けられます。自分の PKCE の組、自分のループバックのリダイレクト先、 そして自分が持つトークンです。
PKCE(RFC 7636) はループバックの受け渡しを守ります。使い捨てのゲートウェイコードは、 アプリの外に出ない検証子がなければ役に立ちません。このコードは 1 回きりで、寿命も短く してあります。
対応状況の判定と代替手段
デスクトップ側はゲートウェイの公開エンドポイント /api/status を読みます。ここには auth_flows という配列が載っています。
auth_flows の値 |
意味 |
|---|---|
["cookie", "native_pkce"] |
ゲートウェイがネイティブサインインに対応 → アプリはこちらを使います |
["cookie"] |
ゲートウェイは従来の方式のみ対応 → アプリは埋め込みの WebView を使います |
| *(項目そのものがない)* | 古いゲートウェイ → アプリは埋め込みの WebView を使います |
ネイティブサインインに対応していると出ているのに、手元の事情で失敗することもあります。 たとえばセキュリティソフトがループバックの待ち受けを止めた、ブラウザのタブを閉じた、 といった場合です。そのときアプリは自動的に埋め込みの方式へ切り替えるので、サインインは そのまま続けられます。
トークンの寿命
- アクセストークン: 寿命は短く、数分です。REST の呼び出しのたび、また WebSocket の
チケットを発行するときに Authorization: Bearer として送られます。
- リフレッシュトークン: もう少し長持ちし、使うたびに入れ替わります。アクセストークンの
期限が近づくと、アプリは /auth/native/refresh を呼んで両方のトークンを入れ替え、 保存内容を更新します。
- 完全な失効: リフレッシュトークンが使えなくなった場合(期限切れ、取り消し、再利用の
検知)、アプリは保存していたトークンを消し、あらためてサインインを求めます。
- サインアウト: そのゲートウェイについて、保存したネイティブのトークンと、従来方式の
セッションクッキーの両方を消します。
ゲートウェイを運用する方へ
ネイティブサインインは、対話型のセッションプロバイダを登録した認証付きゲートウェイなら 自動的に使えます。設定は不要です。/auth/native/* の経路と auth_flows の表明は、 ダッシュボードの認証機構の一部だからです。OAuth のプロバイダ(たとえば同梱の Nous プロバイダ)は上流の ID プロバイダへのリダイレクトを仲立ちします。パスワード型の プロバイダ(たとえば同梱の basic-auth プラグイン)の場合は、システムのブラウザが ゲートウェイの /login の入力画面に着地します。これによって OS のパスワード管理 (macOS のパスワードなど)が入力欄を自動補完でき、埋め込みのデスクトップ WebView には 真似できない使い勝手になります。トークンだけの認証情報(drain など)は対話型のサインイン ではないので、native_pkce を表明しません。
関係するエンドポイントは次のとおりです(いずれも公開で、認証前の入口として、既存の /auth/* の OAuth 経路と同じ扱いです)。
GET /auth/native/authorize— 仲立ちされた PKCE ログインを始めますPOST /auth/native/token— ループバックのコードと検証子をトークンに交換しますPOST /auth/native/refresh— アプリのリフレッシュトークンから、トークンを入れ替えます
関連ページ
- SSH 越し・リモートホストでの OAuth — 離れた端末で
プロバイダや MCP の OAuth を行うための、ループバックで受け取る手順です。