Sign-in and security
Browser-based device authorization sign-in plus the shell's security boundaries - CSP, external links, the OS keychain, and the deny-by-default preload bridge.
No password form. Sign-in runs the OAuth 2.0 Device Authorization Grant via the Better Auth deviceAuthorization and bearer plugins (the desktop client uses client_id "desktop"):
${baseUrl}/auth/device?user_code=XXXX and shows the user code.safeStorage through a validated IPC handler (window.api.auth), never exposed to the renderer's JavaScript.The verification page lives in your web frontend's /auth/device page and its deviceAuthorizationClient wiring. See your project's Authentication guide for the shared Better Auth setup.
Security and native boundaries
The renderer is fully isolated - contextIsolation on, sandbox on, nodeIntegration off - and reaches the main process only through the narrow, validated preload bridge (window.api); the raw ipcRenderer is never exposed. Every main-process handler validates its inputs. Boundaries to know when you add features:
| Boundary | Behavior | Where to extend |
|---|---|---|
| Content-Security-Policy | connect-src is 'self' and names no backend origin: the renderer never fetches the backend directly - every backend call rides the main-process bridge (backend-transport.ts), which CSP does not govern. img-src allows any HTTPS image (avatars from social/OAuth providers, etc.). | Repoint the backend by setting config.desktop.baseUrl (pinned at build time in apps/electron/src/main/backend-url.ts), not connect-src. To restrict image hosts, edit img-src in buildCspPolicy (apps/electron/src/main/csp-policy.ts). |
| External links | Open in the system browser via window.api.shell.openExternal, which accepts only http(s) URLs (isHttpUrl), so a stray file: or javascript: link cannot be opened. New windows are always denied. | - |
| Navigation | Top-level navigation and redirects are allowlisted to the app's own document (the dev server origin, or the packaged renderer's file: document); anything else is blocked, so the bridged renderer can never be pointed at an attacker origin. | - |
| Auth token | Stored in the OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service) via Electron safeStorage; a dev-only file-backed fallback is used when safeStorage is unavailable. Never in the renderer's web storage. | - |
| IPC surface | Every method the renderer may call is an explicit window.api.* entry backed by one validating ipcMain.handle; there is no general-purpose bridge. | Add a typed method to the preload bridge + a validating handler in apps/electron/src/main/ipc.ts. |
| Device sign-in | Accepts any client_id by default (the desktop sends "desktop"). | Set validateClient on the deviceAuthorization plugin in @repo/auth to restrict clients in production. |
CSP is enforced two ways from one policy builder (csp-policy.ts) so dev and packaged cannot drift: the dev server gets a header CSP (onHeadersReceived), and the packaged file: renderer gets a build-time <meta> CSP (header CSP does not apply to file:).
A CSP-blocked request surfaces only as a console CSP violation, not a thrown error - but CSP governs only the renderer's own resources, such as images under img-src. Backend calls do not go through CSP: they ride the main-process bridge, so a failing API call is a bridge or backend-URL issue - confirm config.desktop.baseUrl points at the backend you expect, not connect-src.
The renderer never reaches your backend directly. It hands the main process a request PATH; main composes the URL against the origin pinned at build time from config.desktop.baseUrl and refuses any path that resolves off it, so a compromised renderer cannot point main's bearer-carrying fetch at another host. Auth is a bearer token (not cookies), and main sends no Origin header in dev or packaged builds, so the backend treats it as a native client and never CORS-checks it - there is no dev-vs-packaged origin to configure. Your backend's TRUSTED_ORIGINS (config.origins) still governs your web frontends - see your project's Configuration guide.