@bigio/better-auth-electron 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,40 +1,34 @@
1
1
  ## I'm currently studying the official implementation; the documentation is still rough but will be improved soon. I'm eager and looking forward to exchanging ideas with everyone.
2
2
 
3
3
  Based on my study of the official implementation code, I have decided on the following to-do list:
4
- **1. Architecture: The "Silent Handoff" (Stateless & Secure)**
5
4
 
6
- * [ ] **Server-Side Cookie Interception**: Modify `electron-server-plugin` to intercept the OAuth callback response.
7
- * *Action*: Strip the `Set-Cookie` header (specifically the session token) from the response to prevent overwriting the user's browser session.
8
- * *Goal*: Achieve strict physical isolation between Web Session and Electron Session.
5
+ **~~1. Architecture: The "Silent Handoff" (Stateless & Secure)~~**
9
6
 
7
+ - [done] ~~**Server-Side Cookie Interception**: Modify `electron-server-plugin` to intercept the OAuth callback response.~~
8
+ - ~~_Action_: Strip the `Set-Cookie` header (specifically the session token) from the response to prevent overwriting the user's browser session.~~
9
+ - ~~_Goal_: Achieve strict physical isolation between Web Session and Electron Session.~~
10
10
 
11
- * [ ] **Stateless OAuth Flow**: Ensure the OAuth flow relies solely on the encrypted `Ticket` mechanism, making the browser a purely stateless transport layer for Electron authentication.
11
+ - ~~**Stateless OAuth Flow**: Ensure the OAuth flow relies solely on the encrypted `Ticket` mechanism, making the browser a purely stateless transport layer for Electron authentication.~~
12
12
 
13
13
  **2. Security & Hardening**
14
14
 
15
- * [ ] **Secure Persistence**: Implement `safeStorage` (DPAPI/Keychain) for encrypting the persisted PKCE Verifier on disk.
16
- * *Reason*: Prevent plaintext credentials from resting on the file system.
17
-
18
-
19
- * [ ] **User-Agent Scrubbing**: Global removal of "Electron" tokens from the `User-Agent` string at the `app.on('ready')` stage.
20
- * *Reason*: Bypass WAF/Anti-Bot protections that block Electron-based requests during the ticket exchange phase.
21
-
22
-
23
- * [ ] **Automated CSP Injection**: Implement `onHeadersReceived` interceptor in the Main Process.
24
- * *Action*: Automatically append the backend API URL to the `connect-src` directive.
25
- * *Goal*: Provide a "Zero-Config" experience by preventing CSP violations without requiring users to manually edit `index.html`.
15
+ - [ ] **Secure Persistence**: Implement `safeStorage` (DPAPI/Keychain) for encrypting the persisted PKCE Verifier on disk.
16
+ - _Reason_: Prevent plaintext credentials from resting on the file system.
26
17
 
18
+ - [ ] **User-Agent Scrubbing**: Global removal of "Electron" tokens from the `User-Agent` string at the `app.on('ready')` stage.
19
+ - _Reason_: Bypass WAF/Anti-Bot protections that block Electron-based requests during the ticket exchange phase.
27
20
 
21
+ - [done] ~~**Automated CSP Injection**: Implement `onHeadersReceived` interceptor in the Main Process.~~
22
+ - ~~_Action_: Automatically append the backend API URL to the `connect-src` directive.~~
23
+ - ~~_Goal_: Provide a "Zero-Config" experience by preventing CSP violations without requiring users to manually edit `index.html`.~~
28
24
 
29
25
  **3. Developer Experience (DX) & API**
30
26
 
31
- * [ ] **Enhanced Renderer API**: Refactor `getActions` to introduce a dedicated `authClient.bigio` namespace.
32
- * *Feature*: Implement `authClient.bigio.signIn({ provider: 'github' })` wrapper.
33
- * *Implementation*: Utilize `window.open` (intercepted by Main) or IPC to trigger the flow, keeping the API consistent with the official web client style.
34
-
35
-
36
- * [ ] **Smart Web Handoff UI (Optional/Next)**: Update the web-side confirmation page to detect and display the currently logged-in web user, offering a "Continue as [User]" button for a seamless transition.
27
+ - [ ] **Enhanced Renderer API**: Refactor `getActions` to introduce a dedicated `authClient.bigio` namespace.
28
+ - _Feature_: Implement `authClient.bigio.signIn({ provider: 'github' })` wrapper.
29
+ - _Implementation_: Utilize `window.open` (intercepted by Main) or IPC to trigger the flow, keeping the API consistent with the official web client style.
37
30
 
31
+ - [done] ~~**Smart Web Handoff UI (Optional/Next)**: Update the web-side confirmation page to detect and display the currently logged-in web user, offering a "Continue as [User]" button for a seamless transition.~~
38
32
 
39
33
  # @bigio/better-auth-electron
40
34
 
@@ -77,6 +71,14 @@ pnpm add better-auth electron react react-dom
77
71
 
78
72
  Initialize Better Auth with the `electronServerPlugin`. This handles the ticket exchange and verification logic on your backend.
79
73
 
74
+ #### The "Silent Handoff" Mechanism (Stateless & Secure)
75
+
76
+ This plugin implements a **Server-Side Cookie Interception** strategy to ensure strict isolation between the Web Session and the Electron Session.
77
+
78
+ - It intercepts OAuth callback responses specifically for Electron. It actively **removes the `Set-Cookie` header** (which contains the session token) before the response reaches the browser.
79
+ - This guarantees that the Electron login flow **does not overwrite or interfere** with the user's existing browser session.
80
+ - Authentication relies solely on a one-time encrypted Ticket. The browser acts as a purely **`stateless`** transport layer for Electron.
81
+
80
82
  ```typescript
81
83
  import { betterAuth } from 'better-auth'
82
84
  import { electronServerPlugin } from '@bigio/better-auth-electron/server'
@@ -102,6 +104,28 @@ export const auth = betterAuth({
102
104
 
103
105
  Use `mainInjection` to setup IPC handlers and deep linking strategies. This automatically handles the "protocol" opening events.
104
106
 
107
+ ### Security & CSP Configuration
108
+
109
+ ** IMPORTANT: Clean up your `index.html`**
110
+
111
+ This plugin automatically injects a rigorous, production-ready **Content Security Policy (CSP)** via the Main Process.
112
+
113
+ **You CAN remove** any manual CSP `<meta>` tags from your `index.html` (renderer). Leaving them in will cause the browser to enforce the "intersection" of both policies, likely breaking your Auth flow (e.g., blocking the OAuth popup or API connection).
114
+
115
+ **DELETE this from your `index.html`:**
116
+
117
+ ```html
118
+ <meta
119
+ http-equiv="Content-Security-Policy"
120
+ content="
121
+ default-src 'self';
122
+ script-src 'self';
123
+ style-src 'self' 'unsafe-inline';
124
+ img-src 'self' data:;
125
+ connect-src 'self' http://localhost;
126
+ " />
127
+ ```
128
+
105
129
  ```typescript
106
130
  import { app, BrowserWindow } from 'electron'
107
131
  import { mainInjection } from '@bigio/better-auth-electron/main'
@@ -114,8 +138,13 @@ const { windowInjection, whenReadyInjection } = mainInjection({
114
138
  PROVIDERS: ['github', 'google'],
115
139
  BETTER_AUTH_BASEURL: 'http://localhost:3002',
116
140
  FRONTEND_URL: 'http://localhost:3001/oauth',
117
- // Use the classic 'onBeforeRequest' filter approach for auth code capture if true
118
- OLD_SCHOOL_ONBEFORE_WAY: false,
141
+ /**
142
+ * [Optional] Content Security Policy (CSP) Configuration
143
+ * * Strategy: "All-or-Nothing"
144
+ * - undefined (Default): The plugin automatically injects a secure, production-ready CSP (The "MVP" Fallback).
145
+ * - string: The plugin uses YOUR string exactly. No merging, no magic. You take full control.
146
+ */
147
+ CONTENT_SECURITY_POLICY: "default-src 'self'; ...", // override completely
119
148
  })
120
149
 
121
150
  function createWindow(): void {
@@ -134,6 +163,21 @@ app.whenReady().then(() => {
134
163
  })
135
164
  ```
136
165
 
166
+ **If CONTENT_SECURITY_POLICY is not provided, the plugin applies the following strictly secure rules to the Main Frame (index.html) automatically. This ensures Auth works out-of-the-box while keeping your app secure.**
167
+
168
+ ```http
169
+ default-src 'self';
170
+ script-src 'self';
171
+ style-src 'self' 'unsafe-inline';
172
+ # Allows loading images from 'self', OAuth providers (https:), and your Auth Server
173
+ img-src 'self' data: blob: https: ${BETTER_AUTH_BASEURL};
174
+ # Strictly restricts API connections to 'self' and your Auth Server
175
+ connect-src 'self' ${BETTER_AUTH_BASEURL};
176
+ font-src 'self' data:;
177
+ # Prevents clickjacking attacks
178
+ frame-ancestors 'none';
179
+ ```
180
+
137
181
  ### 3. Web Client Initialization (`src/web/client.ts`)
138
182
 
139
183
  Configure the client-side plugin. Note the usage of `setLazyClient` to handle circular dependencies or lazy initialization patterns effectively.
@@ -158,7 +202,31 @@ export const authClient = createAuthClient({
158
202
  setLazyClient(authClient)
159
203
  ```
160
204
 
161
- ### 4. Electron Renderer / Login Page (`src/renderer/pages/login.tsx`)
205
+ ### 4. Electron Renderer/Web Client (`src/renderer/lib/auth-client.ts`)
206
+
207
+ This is the auth client running **inside your Electron app**. It listens for the custom protocol deep link to hydrate the session.
208
+
209
+ > **Suggestion:** set `credentials: 'include'` to ensure the session cookie generated by the secure protocol is correctly persisted.
210
+
211
+ ```typescript
212
+ import { createAuthClient } from 'better-auth/react'
213
+ import { electronRendererPlugin } from '@bigio/better-auth-electron/renderer'
214
+
215
+ export const authClient = createAuthClient({
216
+ baseURL: 'http://localhost:3002',
217
+ fetchOptions: {
218
+ // It ensures cookies are sent/received correctly in the custom scheme.
219
+ credentials: 'include',
220
+ },
221
+ plugins: [
222
+ electronRendererPlugin({
223
+ ELECTRON_SCHEME: 'bigio', // Must match Main process config
224
+ }),
225
+ ],
226
+ })
227
+ ```
228
+
229
+ ### 5. Electron Renderer / Login Page (`src/renderer/pages/login.tsx`)
162
230
 
163
231
  In your Electron renderer (the UI), use the helper options to construct the correct OAuth URL that opens in the system's default browser.
164
232
 
@@ -188,56 +256,65 @@ const ElectronLoginButton = ({ provider }: { provider: string }) => {
188
256
  }
189
257
  ```
190
258
 
191
- ### 5. Electron Renderer/Web Client (`src/renderer/lib/auth-client.ts`)
192
-
193
- This is the auth client running **inside your Electron app**. It listens for the custom protocol deep link to hydrate the session.
194
-
195
- > **Suggestion:** set `credentials: 'include'` to ensure the session cookie generated by the secure protocol is correctly persisted.
259
+ ### 6. Web/App Component Usage (`src/web/components/user-session.tsx`)
196
260
 
197
- ```typescript
198
- import { createAuthClient } from 'better-auth/react'
199
- import { electronRendererPlugin } from '@bigio/better-auth-electron/renderer'
261
+ The `useElectronOAuthSession` hook is the core of the "Handoff" experience. It manages the synchronization between the web authentication state and the Electron application.
200
262
 
201
- export const authClient = createAuthClient({
202
- baseURL: 'http://localhost:3002',
203
- fetchOptions: {
204
- // It ensures cookies are sent/received correctly in the custom scheme.
205
- credentials: 'include',
206
- },
207
- plugins: [
208
- electronRendererPlugin({
209
- ELECTRON_SCHEME: 'bigio', // Must match Main process config
210
- }),
211
- ],
212
- })
213
- ```
263
+ #### Component Implementation
214
264
 
215
- ### 6. Web/App Component Usage (`src/web/components/user-session.tsx`)
265
+ The hook provides reactive states to manage the UI. Most importantly, the 'pending' state serves as a "Session Detected" signal.
216
266
 
217
- The `useElectronOAuthSession` hook is the heart of the "Handoff" experience. It listens for the deep link callback and automatically verifies the session.
267
+ To resolve this state, you use the `setFastLogin` function. Calling this function immediately updates the oauthStatus and triggers the next step in the authentication flow.
218
268
 
219
- ```typescript
269
+ ```tsx
270
+ import { useEffect } from 'react'
220
271
  import { authClient } from '@/web/client'
221
272
 
222
273
  export function UserSessionStatus() {
223
274
  const {
224
- data: useSessionData,
275
+ data: sessionData,
225
276
  error,
226
- isPending,
227
- isRefetching,
228
- refetch,
229
- // // The current status of the handoff process on the client side
230
- // (e.g., 'idle' | 'succeed' | 'failed')
231
- oauthMessage
277
+ isPending, // Initial loading state
278
+
279
+ // Status enum: 'idle' | 'pending' | 'connecting' | 'succeed' | 'failed'
280
+ // 'pending': CRITICAL state. It confirms a valid session ALREADY exists
281
+ // and the system is pausing to wait for the user's decision.
282
+ oauthStatus,
283
+ oauthError,
284
+
285
+ // Action to control the flow:
286
+ // setFastLogin(true) = Fast Login (Use current session)
287
+ // setFastLogin(false) = Switch Account (Ignore current session)
288
+ setFastLogin,
232
289
  } = authClient.bigio.useElectronOAuthSession()
233
290
 
234
- if (isPending) return <div>Loading session... {oauthMessage}</div>
235
- if (error) return <div>Error: {error.message}</div>
236
-
291
+ /**
292
+ * Optional: Force Logic (Auto-decision)
293
+ * If you want to skip the user choice UI:
294
+ */
295
+ useEffect(() => {
296
+ setFastLogin(true) // Force Fast Login immediately
297
+ // OR
298
+ setFastLogin(false) // Force Switch Account immediately
299
+ }, [])
300
+
301
+ /**
302
+ * Optional: User-decision
303
+ * If you want to let the user choice:
304
+ */
237
305
  return (
238
306
  <div>
239
- <h1>Welcome, {useSessionData?.user.name}</h1>
240
- <p>Status: {oauthMessage || 'Idle'}</p>
307
+ {/* The 'pending' status indicates a session collision/detection.
308
+ We present the choice to the user here.
309
+ */}
310
+ {oauthStatus === 'pending' ? (
311
+ <>
312
+ {/* Option: Ignore current session and re-login */}
313
+ <button onClick={() => setFastLogin(false)}>Switch Account</button>
314
+ {/* Option: Use current session for Electron */}
315
+ <button onClick={() => setFastLogin(true)}>Fast Login</button>
316
+ </>
317
+ ) : null}
241
318
  </div>
242
319
  )
243
320
  }