Files
Swarm/docs/WebLocalCredentialSharing.md
2025-11-30 00:00:13 +08:00

206 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Web to Local Credential Sharing for Flutter Desktop Apps
This document outlines the essential features and concepts for implementing secure web to local credential sharing for Flutter desktop applications. The goal is to allow a web application to establish an authenticated session with a running desktop application, leveraging the desktop app's existing authentication and maintaining a secure, hierarchical session structure.
## Core Concepts from DysonNetwork.Pass Refactoring
When accessing the Pass service through the Gateway, replace the `/api` with `/pass`
The recent refactoring of the authentication system in `DysonNetwork.Pass` introduces key mechanisms that directly support this web-to-local credential sharing:
1. **Parent/Sub-Sessions (`SnAuthSession.ParentSessionId`)**:
* The `SnAuthSession` model now includes a `ParentSessionId` field. This allows an authenticated session to explicitly declare that it was derived from another session.
* This is crucial for the web-to-local flow, as the web session can be established as a child of the desktop app's primary session.
2. **Recursive Session Revocation**:
* The `AuthService.RevokeSessionAsync` method has been updated to recursively revoke all child sessions (and their children) when a parent session is logged out.
* This ensures that if a user logs out of their desktop application, all web sessions that were derived from that desktop session are also automatically invalidated, enhancing security and maintaining consistency.
3. **Login from Existing Session API (`AuthController.LoginFromSession`)**:
* A new API endpoint `POST /api/auth/login/session` has been added to `AuthController`.
* This endpoint is designed to create a new `SnAuthSession` (and issue a corresponding authentication token/cookie) by leveraging an *existing* authenticated session.
* It takes device information (`DeviceId`, `DeviceName`, `Platform`, `ExpiredAt`) and the `ParentSessionId` is implicitly set to the `currentSession` available in the `HttpContext`.
* This endpoint is the server-side counterpart to the desktop app's `/exchange` endpoint, allowing the desktop app to request a new, child session for the web application.
## Integration into the Web-to-Local Flow
The `AuthController.LoginFromSession` API endpoint plays a central role in the web-to-local credential sharing mechanism. After the Flutter desktop app's local HTTP server receives and verifies a server-signed challenge from the web app (via its `/exchange` endpoint), the desktop app would then call this `LoginFromSession` API endpoint.
By making this call:
* The desktop app, being already authenticated with the server, provides its active session context.
* The `LoginFromSession` endpoint uses this context to create a *new* session for the web application.
* This new web session is automatically linked to the desktop app's session via `ParentSessionId`.
* A new web session token (e.g., a JWT) is issued for the web app.
This setup ensures that:
* The web session is securely tied to the desktop session.
* The web session benefits from the recursive revocation logic, meaning if the desktop app session is terminated, the web session is also automatically invalidated.
---
## ✅ Feature Checklist for the Flutter Desktop App
1. **Localhost HTTP Server**
Your Flutter desktop app must include:
* A lightweight HTTP server (`dart:io HttpServer`)
* Bind to `127.0.0.1` only (never `0.0.0.0`)
* Use a random port on startup (e.g., `4000060000`)
* Store this port in memory
Endpoints required:
1. `GET /alive`
* Used by the web app to detect that the desktop app is running
* Returns JSON: `{ "status": "ok", "challenge": "<randomChallenge>" }`
2. `POST /exchange`
* Web app sends the server-signed challenge back
* Desktop verifies and replies with a signed token/session
* **Crucially, this is where the desktop app would call the `POST /api/auth/login/session` endpoint on the backend, using its existing session to create a new sub-session for the web app.**
3. `POST /handshake/done` (optional)
* For cleanup, closing UI, etc.
4. `GET /handshake` (optional)
* For some client information and ensure it's Solian's app
---
2. **Challenge/Response Security System**
To avoid any malicious website calling your localhost server, implement:
Desktop app responsibilities:
* Generate a random challenge string (length `3264`)
* Include it in `/alive` response
* When receiving `/exchange`, verify:
* The challenge was signed by your backend
* The signature or token is valid
* Only then return a desktop session token
Requirements:
* Challenge must be valid only once
* Challenge must expire in `≤ 30` seconds
* Challenge tied to desktop session ID
---
3. **Communication With Your Backend**
The desktop app must:
* Use its existing authentication session (local token, refresh token, etc.)
* When receiving the signed challenge from web app:
1. Send the challenge + desktop login token to backend
2. Backend verifies that:
* Desktop user is authenticated
* Challenge matches web request
* Receive a web-session-token from backend (This is the token issued by `AuthController.LoginFromSession`)
* Return it to the web app via `/exchange`
---
4. **Local HTTP Server CORS Headers**
Your desktop server must include:
`Access-Control-Allow-Origin: https://your-web-domain.com`
`Access-Control-Allow-Headers: *`
`Access-Control-Allow-Methods: GET, POST, OPTIONS`
Also allow:
* Preflight `OPTIONS` requests
This lets browser JavaScript call your localhost server directly.
---
5. **Random Port Broadcasting**
On startup, the desktop app picks a random port and exposes:
* `/alive`
* `/exchange`
But the web app needs to know the port.
Two solutions:
Option A (simple):
Web app scans 20 known ports (e.g., `4100041020`).
Option B (secure):
Desktop app writes port to:
* macOS: `~/Library/Application Support/MyApp/port.json`
* Windows: `%APPDATA%/MyApp/port.json`
* Linux: `~/.config/MyApp/port.json`
Web app then tries only one port if user clicks “Connect Desktop”.
---
6. **Custom Protocol (Optional but helpful)**
Register custom protocol:
`solian://auth/connect`
Used to trigger desktop app if its closed.
Flow:
1. Web app tries localhost discovery
2. If not found → open `solian://auth/connect?some args`
3. Desktop app starts → exposes localhost server
4. Web page retries detection
---
7. **App UI Behavior**
The desktop UI should:
* Run the HTTP server silently in background
* Possibly show a “Connecting to web” indicator
* Close or hide handshake window after success
* Notify user if login sync succeeded
---
8. **Logging**
Implement basic logs:
* Server started on port `XXX`
* Received `/alive`
* Verified challenge
* Sent credentials to web
* Errors or invalid tokens
Logs should never include full tokens.
---
9. **Optional: WebSocket Support**
Not required, but improves performance.
* Web app connects `ws://localhost:<port>/ws`
* Faster challenge exchange
* Real-time two-way handshake
---
## ⭐ Summary: Desktop App Needs to Implement
Core
* Local HTTP server on `127.0.0.1:<random_port>`
* `/alive` endpoint with random challenge
* `/exchange` endpoint to finish login
* CORS + preflight support
* Challenge-response security
* Only one-time challenges
Backend communication
* Desktop app verifies web request with backend
* Backend creates session for web (via `AuthController.LoginFromSession`)
* Desktop returns session token to web
Optional
* Custom URI protocol handler (`solian://`)
* Port broadcast file
* WebSocket tunnel