Desktop Native Sign-In (RFC 8252)
When the Indagis Desktop app connects to a gated gateway (a hosted or self-hosted dashboard that sits behind an OAuth provider), it can sign in two ways:
- Native sign-in (RFC 8252) — the app opens your real system browser, you approve in the browser you already trust, and the app receives tokens it stores as owner-only files in its user-data directory (optionally encrypted with your OS keychain — Settings → Gateway). No embedded webview, no browser session cookies. This is the default whenever the gateway supports it.
- Embedded sign-in (legacy fallback) — the app opens a small in-app browser window and captures the gateway's session cookie. Used automatically when the gateway is an older build that doesn't advertise native sign-in.
You don't choose between these — the app detects what the gateway supports and picks the best one. This page explains what happens and why.
Why native sign-in
Embedding a browser inside a native app for OAuth has well-known downsides: the login page can't see your existing browser session (so you re-type credentials and re-do MFA), password managers and passkeys often don't work, and the app relies on reading a session cookie out of a private webview. RFC 8252 ("OAuth 2.0 for Native Apps") is the industry best practice that avoids all of that: do the authorization in the system browser and hand the app its own tokens.
For Indagis specifically, native sign-in means:
- No embedded webview. The authorization happens in Safari / Chrome / Firefox / Edge — whatever you use — with your logins, extensions, and passkeys intact.
- No session cookies. The app holds an OAuth access token (short-lived)
and refresh token, stored as owner-only files — encrypted at rest via
your OS keychain (Electron
safeStorage) when the opt-in keychain toggle in Settings → Gateway is on. REST calls and WebSocket tickets are authenticated with anAuthorization: Bearerheader, not a cookie jar.
How it works
Desktop app Gateway (/auth/native/*) Indagis Cloud (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
The gateway brokers the flow: it is the authorization server to the
desktop app and an OAuth client to the upstream identity provider (Nous
Portal). This is required because the upstream client_id and permitted
redirect URIs are bound to the gateway's own origin — a desktop app can't be a
direct client of the Portal. The desktop still gets the full RFC 8252
experience: its own PKCE pair, its own loopback redirect, and tokens it owns.
PKCE (RFC 7636) protects the loopback hop: the one-time gateway code is useless without the code verifier, which never leaves the app. The code is single-use and short-lived.
Capability detection & fallback
The desktop reads the gateway's public /api/status endpoint, which advertises
an auth_flows array:
auth_flows value | Meaning |
|---|---|
["cookie", "native_pkce"] | Gateway supports native sign-in → the app uses it |
["cookie"] | Gateway supports only the legacy flow → the app uses the embedded webview |
| (field absent) | Older gateway → the app uses the embedded webview |
If native sign-in is advertised but fails for a local reason — e.g. a security tool blocks the loopback listener, or you close the browser tab — the app falls back to the embedded flow automatically so you can still sign in.
Token lifecycle
- Access token: short-lived (minutes). Sent as
Authorization: Beareron every REST call and when minting a WebSocket ticket. - Refresh token: longer-lived, rotating. When the access token is near
expiry the app calls
/auth/native/refreshto rotate both tokens, then updates its token store. - Terminal expiry: if the refresh token is dead (expired / revoked / reuse-detected), the app clears its stored tokens and prompts a fresh sign-in.
- Sign out: clears both the stored native tokens and any legacy session cookie for that gateway.
For gateway operators
Native sign-in is available automatically on any gated gateway with an
interactive session provider registered. No configuration is required — the
/auth/native/* routes and the auth_flows advertisement are part of the
dashboard-auth subsystem. OAuth providers (e.g. the bundled Nous provider)
broker the upstream IDP redirect; password providers (e.g. the bundled
basic-auth plugin) land the system browser on the gateway's /login
credential form instead — which is what lets OS password managers (macOS
Passwords, etc.) autofill the form, something no embedded desktop webview can
offer. Token-only credentials (e.g. drain) are not interactive sign-ins and do
not advertise native_pkce.
The relevant endpoints (all public, pre-auth bootstrap, same as the existing
/auth/* OAuth routes):
GET /auth/native/authorize— starts the brokered PKCE loginPOST /auth/native/token— exchanges the loopback code + verifier for tokensPOST /auth/native/refresh— rotates tokens from the app's refresh token
See also
- OAuth over SSH / Remote Hosts — the loopback-callback pattern for provider/MCP OAuth on remote machines.
- Run Indagis with Indagis Cloud