桌面原生登录(RFC 8252)

当 Hermes 桌面应用连接到一个受保护网关(一个位于 OAuth 提供商之后的托管或自托管 dashboard)时,它有两种登录方式:

  1. 原生登录(RFC 8252)——应用打开你真实的系统浏览器,在你已经信任的浏览器里完成授权,应用随后拿到令牌,并作为仅属主可读的文件存进其 user-data 目录(可选地用你的系统钥匙串加密——设置 → 网关)。无内嵌 webview,无浏览器会话 cookie。 只要网关支持,这就是默认方式。
  2. 内嵌登录(旧版回退)——应用打开一个小的应用内浏览器窗口,抓取网关的会话 cookie。当网关是一个未声明原生登录的旧构建时自动使用。

你不用在两者间选择——应用探测网关支持什么并选最佳者。本页解释发生了什么、为什么。

为何用原生登录 {#why-native-sign-in}

为 OAuth 在原生应用里内嵌浏览器有众所周知的缺点:登录页看不到你既有浏览器会话(于是你要重输凭据、重做 MFA),密码管理器和 passkey 常常不工作,而且应用依赖从私有 webview 里读一个会话 cookie。RFC 8252("面向原生应用的 OAuth 2.0")是行业最佳实践,避开了这一切:在系统浏览器里完成授权,再把应用自己的令牌交给它。

对 Hermes 而言,原生登录意味着:

  • 无内嵌 webview。 授权发生在 Safari / Chrome / Firefox / Edge——你用哪个就哪个——你的登录态、扩展和 passkey 都完好。
  • 无会话 cookie。 应用持有一个 OAuth access token(短寿命)和 refresh token,作为仅属主可读文件存储——当设置 → 网关中可选的钥匙串开关打开时,经你的系统钥匙串(Electron safeStorage)静态加密。REST 调用和 WebSocket 票据用 Authorization: Bearer 头认证,而非 cookie 罐。

工作原理 {#how-it-works}

桌面应用                网关(/auth/native/*)          Nous Portal(IDP)
   │ 1. 打开 loopback 127.0.0.1:<随机端口>
   │ 2. 系统浏览器 ─►  /auth/native/authorize
   │    (PKCE challenge)(启动正常 PKCE 登录) ─► /oauth/authorize
   │                        ◄──── code ──── /auth/callback ◄──┘
   │                        3. 铸造一次性网关 code
   │ ◄─ 302 127.0.0.1/cb?code=… ─┘
   │ 4. POST /auth/native/token(code + PKCE verifier)
   │ ◄─ 5. { access_token, refresh_token, expires_at } ───────┘
   │ 6. 存入本地令牌库;REST + WS 票据用 Bearer

网关在流程中充当中介:它对桌面应用是授权服务器,对上游身份提供商(Nous Portal)是 OAuth 客户端。这是必需的,因为上游 client_id 和允许的重定向 URI 绑定在网关自己的源上——桌面应用不能直接是 Portal 的客户端。桌面仍获得完整的 RFC 8252 体验:自己的 PKCE 对、自己的 loopback 重定向、以及它自己持有的令牌。

**PKCE(RFC 7636)**保护 loopback 这一跳:一次性网关 code 没有 code verifier 就毫无用处,而 verifier 从不离开应用。code 单次使用、短寿命。

能力探测与回退 {#capability-detection--fallback}

桌面读取网关公开的 /api/status 端点,它声明一个 auth_flows 数组:

auth_flows 值含义
["cookie", "native_pkce"]网关支持原生登录 → 应用使用它
["cookie"]网关只支持旧流程 → 应用使用内嵌 webview
(字段缺失)更旧的网关 → 应用使用内嵌 webview

若声明了原生登录但因本地原因失败——例如安全工具拦截了 loopback 监听器,或你关掉了浏览器标签页——应用自动回退到内嵌流程,让你仍能登录。

令牌生命周期 {#token-lifecycle}

  • Access token:短寿命(分钟级)。每次 REST 调用和铸造 WebSocket 票据时以 Authorization: Bearer 发送。
  • Refresh token:较长寿命,轮换。当 access token 临近过期,应用调用 /auth/native/refresh 轮换两个令牌,然后更新本地令牌库。
  • 终态过期:若 refresh token 失效(过期 / 被吊销 / 检测到重用),应用清空已存令牌并提示重新登录。
  • 登出:清空该网关的已存原生令牌和任何旧版会话 cookie。

给网关运营者 {#for-gateway-operators}

任何注册了交互式会话提供商的受保护网关都自动支持原生登录。无需配置——/auth/native/* 路由和 auth_flows 声明是 dashboard-auth 子系统的一部分。OAuth 提供商(例如内置的 Nous 提供商)中介上游 IDP 重定向;密码提供商(例如内置的 basic-auth 插件)则把系统浏览器落在网关的 /login 凭据表单上——这正是让系统密码管理器(macOS 密码等)自动填充表单的方式,是任何内嵌桌面 webview 都做不到的。纯令牌凭据(例如 drain)不是交互式登录,不声明 native_pkce。

相关端点(全部公开、预引导,与既有 /auth/* OAuth 路由相同):

  • GET /auth/native/authorize——启动中介 PKCE 登录
  • POST /auth/native/token——用 loopback code + verifier 换取令牌
  • POST /auth/native/refresh——用应用的 refresh token 轮换令牌

另见 {#see-also}