8.0 KiB
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:
-
Parent/Sub-Sessions (
SnAuthSession.ParentSessionId):- The
SnAuthSessionmodel now includes aParentSessionIdfield. 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.
- The
-
Recursive Session Revocation:
- The
AuthService.RevokeSessionAsyncmethod 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.
- The
-
Login from Existing Session API (
AuthController.LoginFromSession):- A new API endpoint
POST /api/auth/login/sessionhas been added toAuthController. - 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 theParentSessionIdis implicitly set to thecurrentSessionavailable in theHttpContext. - This endpoint is the server-side counterpart to the desktop app's
/exchangeendpoint, allowing the desktop app to request a new, child session for the web application.
- A new API endpoint
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
LoginFromSessionendpoint 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
-
Localhost HTTP Server
Your Flutter desktop app must include:
- A lightweight HTTP server (
dart:io HttpServer) - Bind to
127.0.0.1only (never0.0.0.0) - Use a random port on startup (e.g.,
40000–60000) - Store this port in memory
Endpoints required:
GET /alive- Used by the web app to detect that the desktop app is running
- Returns JSON:
{ "status": "ok", "challenge": "<randomChallenge>" }
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/sessionendpoint on the backend, using its existing session to create a new sub-session for the web app.
POST /handshake/done(optional)- For cleanup, closing UI, etc.
GET /handshake(optional)- For some client information and ensure it's Solian's app
- A lightweight HTTP server (
-
Challenge/Response Security System
To avoid any malicious website calling your localhost server, implement:
Desktop app responsibilities:
- Generate a random challenge string (length
32–64) - Include it in
/aliveresponse - 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
≤ 30seconds - Challenge tied to desktop session ID
- Generate a random challenge string (length
-
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:
- Send the challenge + desktop login token to backend
- 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
-
Local HTTP Server CORS Headers
Your desktop server must include:
Access-Control-Allow-Origin: https://your-web-domain.comAccess-Control-Allow-Headers: *Access-Control-Allow-Methods: GET, POST, OPTIONSAlso allow:
- Preflight
OPTIONSrequests
This lets browser JavaScript call your localhost server directly.
- Preflight
-
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.,
41000–41020).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”.
-
Custom Protocol (Optional but helpful)
Register custom protocol:
solian://auth/connectUsed to trigger desktop app if it’s closed.
Flow:
- Web app tries localhost discovery
- If not found → open
solian://auth/connect?some args - Desktop app starts → exposes localhost server
- Web page retries detection
-
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
-
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.
- Server started on port
-
Optional: WebSocket Support
Not required, but improves performance.
- Web app connects
ws://localhost:<port>/ws - Faster challenge exchange
- Real-time two-way handshake
- Web app connects
⭐ Summary: Desktop App Needs to Implement
Core
- Local HTTP server on
127.0.0.1:<random_port> /aliveendpoint with random challenge/exchangeendpoint 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