@flink-app/oauth-plugin 0.12.1-alpha.40 → 0.12.1-alpha.43

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.
Files changed (2) hide show
  1. package/README.md +94 -22
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -118,7 +118,7 @@ const app = new FlinkApp<Context>({
118
118
  return {
119
119
  user,
120
120
  token,
121
- redirectUrl: "https://myapp.com/dashboard",
121
+ redirectUrl: "https://myapp.com/dashboard", // Plugin will add: #token=...
122
122
  };
123
123
  },
124
124
 
@@ -196,10 +196,12 @@ onAuthSuccess: async (
196
196
  Promise<{
197
197
  user: any;
198
198
  token: string; // JWT token from ctx.plugins.jwtAuth.createToken()
199
- redirectUrl?: string;
199
+ redirectUrl?: string; // Plugin appends #token=... to this URL
200
200
  }>;
201
201
  ```
202
202
 
203
+ **Important:** The `redirectUrl` should NOT include the token. The plugin automatically appends `#token=...` to the URL you return.
204
+
203
205
  **OAuth Profile Structure:**
204
206
 
205
207
  ```typescript
@@ -264,9 +266,53 @@ interface OAuthError {
264
266
  10. Plugin calls `onAuthSuccess` callback with profile and context
265
267
  11. App creates/links user account
266
268
  12. **App generates JWT token via `ctx.plugins.jwtAuth.createToken()`**
267
- 13. **Plugin returns JWT token to client**
269
+ 13. **Plugin returns JWT token to client via URL fragment**
268
270
  14. Client stores JWT token and uses it for authenticated requests
269
271
 
272
+ ### How to Extract JWT Token in Frontend
273
+
274
+ **IMPORTANT:** The plugin returns the JWT token as a **URL fragment** (`#token=...`), NOT as a query parameter (`?token=...`).
275
+
276
+ URL fragments are more secure because they are:
277
+ - NOT sent to the server in HTTP requests
278
+ - NOT logged in server access logs
279
+ - Only accessible to client-side JavaScript
280
+
281
+ **Correct way to extract the token:**
282
+
283
+ ```javascript
284
+ // ✅ CORRECT - Read from URL fragment (hash)
285
+ const hash = window.location.hash.slice(1); // Remove leading #
286
+ const params = new URLSearchParams(hash);
287
+ const token = params.get("token");
288
+
289
+ // ❌ WRONG - Reading from query parameters won't work
290
+ const params = new URLSearchParams(window.location.search);
291
+ const token = params.get("token"); // This returns null!
292
+ ```
293
+
294
+ **In your `onAuthSuccess` callback:**
295
+
296
+ ```typescript
297
+ // ✅ CORRECT - Just return the redirectUrl, plugin adds #token=...
298
+ return {
299
+ user,
300
+ token,
301
+ redirectUrl: "https://myapp.com/dashboard",
302
+ };
303
+ // Result: https://myapp.com/dashboard#token=eyJ...
304
+
305
+ // ❌ WRONG - Don't manually add the token
306
+ return {
307
+ user,
308
+ token,
309
+ redirectUrl: `https://myapp.com/dashboard?token=${token}`,
310
+ };
311
+ // Plugin will add token again: ...?token=eyJ...#token=eyJ...
312
+ ```
313
+
314
+ See [Client Integration Examples](#client-integration-examples) for complete frontend code.
315
+
270
316
  ### Initiate OAuth Flow
271
317
 
272
318
  ```
@@ -294,11 +340,45 @@ GET /oauth/:provider/callback?code={auth_code}&state={state}&response_type={json
294
340
 
295
341
  - `code` - Authorization code from provider
296
342
  - `state` - CSRF protection token
297
- - `response_type` - Optional response format (default: redirect with token in query)
343
+ - `response_type` - Optional response format: `json` for JSON response, otherwise redirect
298
344
 
299
345
  **Response Formats:**
300
346
 
301
- 1. **JSON Response** (when `response_type=json`):
347
+ 1. **URL Fragment Redirect** (default):
348
+
349
+ The plugin redirects to your `redirectUrl` with the JWT token appended as a **URL fragment** for enhanced security:
350
+
351
+ ```
352
+ https://myapp.com/dashboard#token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
353
+ ```
354
+
355
+ **Why fragments?** URL fragments (`#token=...`) are NOT sent to the server in HTTP requests, making them more secure than query parameters. They're only accessible to client-side JavaScript.
356
+
357
+ **Important:** Do NOT manually add the token to your `redirectUrl`. The plugin automatically appends it:
358
+
359
+ ```typescript
360
+ // ❌ WRONG - Don't do this
361
+ return {
362
+ user,
363
+ token,
364
+ redirectUrl: `${frontendUrl}/auth/callback?token=${token}`, // Plugin will add token again!
365
+ };
366
+
367
+ // ✅ CORRECT - Let plugin append the token
368
+ return {
369
+ user,
370
+ token,
371
+ redirectUrl: `${frontendUrl}/auth/callback`, // Plugin adds: #token=...
372
+ };
373
+ ```
374
+
375
+ 2. **JSON Response** (when `response_type=json`):
376
+
377
+ Use `response_type=json` for API clients that need direct JSON response instead of redirect:
378
+
379
+ ```
380
+ GET /oauth/github/callback?code=xxx&state=yyy&response_type=json
381
+ ```
302
382
 
303
383
  ```json
304
384
  {
@@ -311,18 +391,6 @@ GET /oauth/:provider/callback?code={auth_code}&state={state}&response_type={json
311
391
  }
312
392
  ```
313
393
 
314
- 2. **URL Fragment** (when redirect URL supports fragments):
315
-
316
- ```
317
- https://myapp.com/dashboard#token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
318
- ```
319
-
320
- 3. **Query Parameter** (default):
321
-
322
- ```
323
- https://myapp.com/dashboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
324
- ```
325
-
326
394
  ## Context API
327
395
 
328
396
  The plugin exposes methods via `ctx.plugins.oauth`:
@@ -531,15 +599,16 @@ function LoginPage() {
531
599
  };
532
600
 
533
601
  React.useEffect(() => {
534
- // Check for token in URL after OAuth redirect
535
- const urlParams = new URLSearchParams(window.location.search);
536
- const token = urlParams.get("token");
602
+ // IMPORTANT: Token is in URL FRAGMENT (#token=...), not query parameter (?token=...)
603
+ const hash = window.location.hash.slice(1); // Remove the leading #
604
+ const params = new URLSearchParams(hash);
605
+ const token = params.get("token");
537
606
 
538
607
  if (token) {
539
608
  // Store JWT token
540
609
  localStorage.setItem("jwt_token", token);
541
610
 
542
- // Clean URL
611
+ // Clean URL (remove fragment)
543
612
  window.history.replaceState({}, document.title, "/dashboard");
544
613
 
545
614
  // Redirect to dashboard
@@ -567,7 +636,10 @@ async function loginWithGitHub() {
567
636
 
568
637
  if (result.type === "success") {
569
638
  const url = result.url;
570
- const token = new URL(url).searchParams.get("token");
639
+
640
+ // IMPORTANT: Token is in URL FRAGMENT (#token=...), not query parameter (?token=...)
641
+ const urlObj = new URL(url);
642
+ const token = new URLSearchParams(urlObj.hash.slice(1)).get("token");
571
643
 
572
644
  if (token) {
573
645
  await AsyncStorage.setItem("jwt_token", token);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/oauth-plugin",
3
- "version": "0.12.1-alpha.40",
3
+ "version": "0.12.1-alpha.43",
4
4
  "description": "Flink plugin for OAuth 2.0 authentication with GitHub and Google providers",
5
5
  "scripts": {
6
6
  "test": "node --preserve-symlinks -r ts-node/register -- node_modules/jasmine/bin/jasmine --config=./spec/support/jasmine.json",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "devDependencies": {
22
22
  "@flink-app/flink": "^0.12.1-alpha.40",
23
- "@flink-app/jwt-auth-plugin": "^0.12.1-alpha.40",
23
+ "@flink-app/jwt-auth-plugin": "^0.12.1-alpha.43",
24
24
  "@flink-app/test-utils": "^0.12.1-alpha.40",
25
25
  "@types/jasmine": "^3.7.1",
26
26
  "@types/jwt-simple": "^0.5.36",
@@ -34,5 +34,5 @@
34
34
  "tsc-watch": "^4.2.9",
35
35
  "typescript": "5.4.5"
36
36
  },
37
- "gitHead": "456502f273fe9473df05b71a803f3eda1a2f8931"
37
+ "gitHead": "e5fc78243a97075ce0272f287f3f89fd44681715"
38
38
  }