Skip to content

Desktop auth: token-returning login via external browser + deep-link redirect #285

Description

@martsokha

Context

The web console moved to cookie-session auth (SDK 0.47): login/signup/OIDC set the HttpOnly nvisy.session + readable nvisy.csrf cookies and return no token. That works for the browser, but it breaks the Tauri desktop app, and the fix needs a server capability that doesn't exist yet.

Why desktop can't use cookies (verified): the desktop routes all API calls through Tauri's Rust HTTP client (tauri-plugin-http) — it must, because the webview can't reach an arbitrary self-hosted API origin (CORS). That Rust client has its own reqwest cookie jar, invisible to the webview's document.cookie, and a bare HttpOnly session cookie (no Max-Age) isn't even persisted across app restarts. So the desktop can't read nvisy.csrf for the CSRF echo, can't observe the session via document.cookie, and can't keep a session across launches. Cookie auth is structurally the wrong fit for the desktop.

Chosen desktop approach: external-browser auth (the gh / Linear / Slack pattern). The desktop opens the user's real system browser to log in — which is also the only way OIDC (Google/Microsoft) can work on desktop, since Google blocks OAuth inside embedded webviews — and the server hands an API token back to the app via a custom deep-link redirect. The token is then sent as Authorization: Bearer … on the Rust HTTP client (the SDK still supports apiToken; the auth extractor already falls back to Bearer when there's no session cookie — jwt_header.rs).

What the server needs to provide

A desktop authorization flow whose callback, on success, redirects to a registered deep-link URI with an API token in the URL (never a Set-Cookie — the app's webview can't see it).

1. Accept a deep-link redirect target

  • Add nvisy://auth/callback (final scheme TBD) to the allowed redirect targets, alongside the existing OIDC_ALLOWED_REDIRECT_ORIGINS. A custom scheme, not an http origin — the validation in auth_oidc.rs (begin_flow rejects non-allow-listed targets, line ~312) needs to permit it.
  • This is a desktop-only target; keep it separate from the web frontend origins.

2. Both password and OIDC in the browser, one token out

The browser-based desktop login should support both:

  • Password: a server-hosted login page (or reuse the existing web login) that authenticates and, for a desktop-originated flow, ends by redirecting to the deep-link with a token.
  • OIDC (Google/Microsoft): reuse the existing /auth/{provider}/start + callback. The providers still redirect to the server (unchanged); only the server's final hop changes — for a desktop flow it emits a token to nvisy://auth/callback instead of setting a cookie on a web origin.

3. Return the token in the callback URL

Reuse the existing fragment-token mechanism (redirect_to_frontend already supports RedirectResult::Token { name: "token", value } in auth_oidc.rs) — the desktop callback should redirect to:

nvisy://auth/callback#token=<api_jwt>

(fragment preferred over query, consistent with the current sign-in token return; ?signin=error on failure). The token is a normal API JWT the desktop stores in its OS keychain and sends as Authorization: Bearer.

4. Distinguish desktop vs web at flow start

The flow needs to know it's a desktop flow (→ emit a token) vs a web flow (→ set a cookie). Most natural: infer it from the redirect target — a nvisy:// target means desktop/token, an http origin means web/cookie. (Alternatively an explicit client=desktop param.) Whichever you pick, please note it in the response so the SDK/client can match.

Contract summary (what the client will implement against)

  • Start: desktop opens the system browser to a server authorize URL with redirect_uri=nvisy://auth/callback.
  • In-browser: user authenticates (password and/or Google/Microsoft) on the server's own origin.
  • Callback: server redirects to nvisy://auth/callback#token=<jwt> on success (?signin=error on failure). No Set-Cookie on this response.
  • After: desktop stores the token in the keychain, sends it as Authorization: Bearer. The existing Bearer path in the auth extractor already accepts it.

Notes / open questions for the server side

  • Final scheme name (nvisy:// vs something else) — client will match.
  • Token lifetime for desktop: should this mint a longer-lived API token (like the api-tokens endpoint) rather than a short session JWT, so desktop doesn't silently expire? Your call — flag what you choose.
  • Does the deep-link redirect target belong in a new env var (e.g. DESKTOP_ALLOWED_REDIRECT_SCHEMES) separate from OIDC_ALLOWED_REDIRECT_ORIGINS?
  • Confirm the provider (Google/Microsoft) redirect URIs stay pointed at the server callback (they should — only the server's final hop is the deep link).

The web cookie flow stays exactly as-is; this is purely the additional desktop path. The desktop client work (deep-link plugin, keychain, Bearer client) is scoped and ready to implement once this contract lands.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    featrequest for or implementation of a new featureserverAPI handlers, middleware, auth

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions