@civic/auth 0.8.2-beta.1 → 0.8.3-beta.1

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 (132) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +6 -0
  3. package/dist/lib/oauth.d.ts +4 -2
  4. package/dist/lib/oauth.d.ts.map +1 -1
  5. package/dist/lib/oauth.js +4 -2
  6. package/dist/lib/oauth.js.map +1 -1
  7. package/dist/nextjs/NextClientAuthenticationRefresher.d.ts +1 -1
  8. package/dist/nextjs/NextClientAuthenticationRefresher.d.ts.map +1 -1
  9. package/dist/nextjs/NextClientAuthenticationRefresher.js.map +1 -1
  10. package/dist/nextjs/NextServerAuthenticationRefresherImpl.d.ts +1 -1
  11. package/dist/nextjs/NextServerAuthenticationRefresherImpl.d.ts.map +1 -1
  12. package/dist/nextjs/NextServerAuthenticationRefresherImpl.js +3 -0
  13. package/dist/nextjs/NextServerAuthenticationRefresherImpl.js.map +1 -1
  14. package/dist/nextjs/routeHandler.d.ts.map +1 -1
  15. package/dist/nextjs/routeHandler.js +2 -1
  16. package/dist/nextjs/routeHandler.js.map +1 -1
  17. package/dist/reactjs/core/GlobalAuthManager.d.ts +15 -0
  18. package/dist/reactjs/core/GlobalAuthManager.d.ts.map +1 -1
  19. package/dist/reactjs/core/GlobalAuthManager.js +26 -1
  20. package/dist/reactjs/core/GlobalAuthManager.js.map +1 -1
  21. package/dist/reactjs/hooks/useUser.d.ts +3 -0
  22. package/dist/reactjs/hooks/useUser.d.ts.map +1 -1
  23. package/dist/reactjs/hooks/useUser.js +32 -0
  24. package/dist/reactjs/hooks/useUser.js.map +1 -1
  25. package/dist/reactjs/providers/CivicAuthContext.d.ts +4 -0
  26. package/dist/reactjs/providers/CivicAuthContext.d.ts.map +1 -1
  27. package/dist/reactjs/providers/CivicAuthContext.js +22 -13
  28. package/dist/reactjs/providers/CivicAuthContext.js.map +1 -1
  29. package/dist/reactjs/providers/CivicAuthProvider.d.ts +1 -0
  30. package/dist/reactjs/providers/CivicAuthProvider.d.ts.map +1 -1
  31. package/dist/reactjs/providers/CivicAuthProvider.js +3 -1
  32. package/dist/reactjs/providers/CivicAuthProvider.js.map +1 -1
  33. package/dist/server/config.d.ts +47 -0
  34. package/dist/server/config.d.ts.map +1 -1
  35. package/dist/server/config.js.map +1 -1
  36. package/dist/server/index.d.ts +8 -2
  37. package/dist/server/index.d.ts.map +1 -1
  38. package/dist/server/index.js +5 -1
  39. package/dist/server/index.js.map +1 -1
  40. package/dist/server/login.d.ts +9 -0
  41. package/dist/server/login.d.ts.map +1 -1
  42. package/dist/server/login.js +4 -2
  43. package/dist/server/login.js.map +1 -1
  44. package/dist/server/refresh.d.ts +1 -1
  45. package/dist/server/refresh.d.ts.map +1 -1
  46. package/dist/server/refresh.js.map +1 -1
  47. package/dist/server/session.d.ts +60 -2
  48. package/dist/server/session.d.ts.map +1 -1
  49. package/dist/server/session.js +216 -5
  50. package/dist/server/session.js.map +1 -1
  51. package/dist/server/types/express.d.ts +97 -0
  52. package/dist/server/types/express.d.ts.map +1 -0
  53. package/dist/server/types/express.js +2 -0
  54. package/dist/server/types/express.js.map +1 -0
  55. package/dist/services/AuthenticationService.d.ts +6 -0
  56. package/dist/services/AuthenticationService.d.ts.map +1 -1
  57. package/dist/services/AuthenticationService.js +48 -6
  58. package/dist/services/AuthenticationService.js.map +1 -1
  59. package/dist/services/types.d.ts +1 -1
  60. package/dist/services/types.d.ts.map +1 -1
  61. package/dist/services/types.js.map +1 -1
  62. package/dist/shared/components/CivicAuthIframe.d.ts +1 -0
  63. package/dist/shared/components/CivicAuthIframe.d.ts.map +1 -1
  64. package/dist/shared/components/CivicAuthIframe.js +4 -4
  65. package/dist/shared/components/CivicAuthIframe.js.map +1 -1
  66. package/dist/shared/components/CivicAuthIframeContainer.d.ts +2 -1
  67. package/dist/shared/components/CivicAuthIframeContainer.d.ts.map +1 -1
  68. package/dist/shared/components/CivicAuthIframeContainer.js +10 -3
  69. package/dist/shared/components/CivicAuthIframeContainer.js.map +1 -1
  70. package/dist/shared/hooks/useSignIn.d.ts.map +1 -1
  71. package/dist/shared/hooks/useSignIn.js +2 -1
  72. package/dist/shared/hooks/useSignIn.js.map +1 -1
  73. package/dist/shared/lib/AuthenticationRefresherImpl.d.ts +2 -2
  74. package/dist/shared/lib/AuthenticationRefresherImpl.d.ts.map +1 -1
  75. package/dist/shared/lib/AuthenticationRefresherImpl.js +3 -0
  76. package/dist/shared/lib/AuthenticationRefresherImpl.js.map +1 -1
  77. package/dist/shared/lib/GenericAuthenticationRefresher.d.ts +2 -2
  78. package/dist/shared/lib/GenericAuthenticationRefresher.d.ts.map +1 -1
  79. package/dist/shared/lib/GenericAuthenticationRefresher.js.map +1 -1
  80. package/dist/shared/lib/iframeUtils.d.ts +1 -0
  81. package/dist/shared/lib/iframeUtils.d.ts.map +1 -1
  82. package/dist/shared/lib/iframeUtils.js +3 -0
  83. package/dist/shared/lib/iframeUtils.js.map +1 -1
  84. package/dist/shared/lib/util.d.ts +7 -0
  85. package/dist/shared/lib/util.d.ts.map +1 -1
  86. package/dist/shared/lib/util.js +12 -0
  87. package/dist/shared/lib/util.js.map +1 -1
  88. package/dist/shared/version.d.ts +1 -1
  89. package/dist/shared/version.js +1 -1
  90. package/dist/shared/version.js.map +1 -1
  91. package/dist/vanillajs/auth/BackendAuthenticationRefresher.d.ts +41 -0
  92. package/dist/vanillajs/auth/BackendAuthenticationRefresher.d.ts.map +1 -0
  93. package/dist/vanillajs/auth/BackendAuthenticationRefresher.js +125 -0
  94. package/dist/vanillajs/auth/BackendAuthenticationRefresher.js.map +1 -0
  95. package/dist/vanillajs/auth/CivicAuth.d.ts +66 -0
  96. package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
  97. package/dist/vanillajs/auth/CivicAuth.js +296 -10
  98. package/dist/vanillajs/auth/CivicAuth.js.map +1 -1
  99. package/dist/vanillajs/auth/SessionManager.d.ts +31 -3
  100. package/dist/vanillajs/auth/SessionManager.d.ts.map +1 -1
  101. package/dist/vanillajs/auth/SessionManager.js +253 -22
  102. package/dist/vanillajs/auth/SessionManager.js.map +1 -1
  103. package/dist/vanillajs/auth/TokenRefresher.d.ts.map +1 -1
  104. package/dist/vanillajs/auth/TokenRefresher.js +31 -18
  105. package/dist/vanillajs/auth/TokenRefresher.js.map +1 -1
  106. package/dist/vanillajs/auth/config/ConfigProcessor.d.ts.map +1 -1
  107. package/dist/vanillajs/auth/config/ConfigProcessor.js +14 -8
  108. package/dist/vanillajs/auth/config/ConfigProcessor.js.map +1 -1
  109. package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts +34 -0
  110. package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts.map +1 -1
  111. package/dist/vanillajs/auth/handlers/IframeAuthHandler.js +139 -0
  112. package/dist/vanillajs/auth/handlers/IframeAuthHandler.js.map +1 -1
  113. package/dist/vanillajs/auth/handlers/MessageHandler.d.ts +21 -0
  114. package/dist/vanillajs/auth/handlers/MessageHandler.d.ts.map +1 -1
  115. package/dist/vanillajs/auth/handlers/MessageHandler.js +52 -2
  116. package/dist/vanillajs/auth/handlers/MessageHandler.js.map +1 -1
  117. package/dist/vanillajs/auth/types/AuthTypes.d.ts +17 -0
  118. package/dist/vanillajs/auth/types/AuthTypes.d.ts.map +1 -1
  119. package/dist/vanillajs/auth/types/AuthTypes.js +1 -0
  120. package/dist/vanillajs/auth/types/AuthTypes.js.map +1 -1
  121. package/dist/vanillajs/iframe/IframeManager.d.ts +36 -0
  122. package/dist/vanillajs/iframe/IframeManager.d.ts.map +1 -1
  123. package/dist/vanillajs/iframe/IframeManager.js +205 -18
  124. package/dist/vanillajs/iframe/IframeManager.js.map +1 -1
  125. package/dist/vanillajs/index.d.ts +2 -0
  126. package/dist/vanillajs/index.d.ts.map +1 -1
  127. package/dist/vanillajs/index.js +4 -0
  128. package/dist/vanillajs/index.js.map +1 -1
  129. package/dist/vanillajs/ui/LoadingComponents.d.ts.map +1 -1
  130. package/dist/vanillajs/ui/LoadingComponents.js +1 -1
  131. package/dist/vanillajs/ui/LoadingComponents.js.map +1 -1
  132. package/package.json +7 -2
@@ -8,7 +8,7 @@ import { AuthenticationEvents } from "./AuthenticationEvents.js";
8
8
  import { PopupError } from "../../services/types.js";
9
9
  import { getVersion } from "../../shared/index.js";
10
10
  import { handleOAuthRedirectPage } from "./handlers/OAuthCallbackHandler.js";
11
- import { generateOauthLogoutUrl, clearTokens, retrieveTokens, } from "../../shared/lib/util.js";
11
+ import { generateOauthLogoutUrl, clearTokens, retrieveTokens, getBackendEndpoints, } from "../../shared/lib/util.js";
12
12
  import { LOGOUT_STATE } from "../../constants.js";
13
13
  import { getOauthEndpoints } from "../../lib/oauth.js";
14
14
  import { collectAndSendSDKAnalytics } from "../../lib/analytics.js";
@@ -17,6 +17,7 @@ import { processConfigWithDefaults } from "./config/ConfigProcessor.js";
17
17
  import { MessageHandler } from "./handlers/MessageHandler.js";
18
18
  import { PopupHandler } from "./handlers/PopupHandler.js";
19
19
  import { IframeAuthHandler } from "./handlers/IframeAuthHandler.js";
20
+ import { IframeManager } from "../iframe/IframeManager.js";
20
21
  /**
21
22
  * CivicAuth client for handling OAuth authentication
22
23
  *
@@ -37,6 +38,8 @@ export class CivicAuth {
37
38
  authProcessTimeoutHandle;
38
39
  popupFailureTimeoutHandle;
39
40
  hasPopupFailed = false;
41
+ // Backend integration - custom login URL
42
+ loginUrl;
40
43
  // Handlers
41
44
  messageHandler;
42
45
  popupHandler;
@@ -49,6 +52,8 @@ export class CivicAuth {
49
52
  // Process config with defaults and validation
50
53
  this.config = processConfigWithDefaults(config);
51
54
  this.initialDisplayMode = this.config.displayMode;
55
+ // Set loginUrl from config if provided
56
+ this.loginUrl = this.config.loginUrl;
52
57
  // Configure logging
53
58
  configureLogging(this.config.logging);
54
59
  // Initialize logger - always use "vanillajs" as base namespace
@@ -68,7 +73,7 @@ export class CivicAuth {
68
73
  // Always initialize events - use provided events or create default instance
69
74
  this.events = config.events || new AuthenticationEvents();
70
75
  // Always initialize SessionManager
71
- this.sessionManager = new SessionManager(this.storage, this.events);
76
+ this.sessionManager = new SessionManager(this.storage, this.events, this.config);
72
77
  // Initialize handlers
73
78
  this.initializeHandlers();
74
79
  }
@@ -82,6 +87,7 @@ export class CivicAuth {
82
87
  *
83
88
  * @example
84
89
  * ```typescript
90
+ * // Standard SPA authentication
85
91
  * const auth = await CivicAuth.create({
86
92
  * clientId: "your-client-id",
87
93
  * // redirectUrl is optional - defaults to current page (window.location.origin + window.location.pathname)
@@ -97,6 +103,12 @@ export class CivicAuth {
97
103
  * success: "Authentication successful!"
98
104
  * }
99
105
  * });
106
+ *
107
+ * // Backend integration authentication
108
+ * const authWithBackend = await CivicAuth.create({
109
+ * clientId: "your-client-id",
110
+ * loginUrl: "http://example.com/custom-backendurl" // Automatically uses BrowserCookieStorage
111
+ * });
100
112
  * ```
101
113
  */
102
114
  static async create(config) {
@@ -128,6 +140,8 @@ export class CivicAuth {
128
140
  oauthServer: this.config.oauthServerBaseUrl,
129
141
  scopes: this.config.scopes,
130
142
  endpoints: this.endpoints,
143
+ loginUrl: this.config.loginUrl || this.loginUrl, // Include loginUrl for backend flows
144
+ backendEndpoints: this.config.backendEndpoints, // Pass backend endpoints config
131
145
  };
132
146
  this.logger.info("🔧 Initializing SessionManager", { authConfig });
133
147
  await this.sessionManager.initializeWithAuthConfig(authConfig);
@@ -148,6 +162,43 @@ export class CivicAuth {
148
162
  else {
149
163
  this.logger.info("🏠 Not a callback page, initialization complete");
150
164
  }
165
+ // Automatically preload iframe if enabled and user is not authenticated
166
+ // This runs after both callback processing and normal initialization
167
+ this.logger.info("🔍 Checking auto-preload conditions", {
168
+ preloadIframe: this.config.preloadIframe,
169
+ displayMode: this.config.displayMode,
170
+ });
171
+ if (this.config.preloadIframe && this.config.displayMode === "iframe") {
172
+ try {
173
+ const isAuthenticated = await this.isAuthenticated();
174
+ this.logger.info("🔍 Authentication check for preload", {
175
+ isAuthenticated,
176
+ });
177
+ if (!isAuthenticated) {
178
+ this.logger.info("🔄 Auto-preloading iframe for instant sign-in...");
179
+ await this.preloadAuthentication();
180
+ this.logger.info("✅ Iframe auto-preloaded successfully");
181
+ }
182
+ else {
183
+ this.logger.debug("ℹ️ User already authenticated, skipping preload");
184
+ }
185
+ }
186
+ catch (error) {
187
+ // Don't fail initialization if preloading fails - graceful degradation
188
+ this.logger.warn("⚠️ Auto-preloading failed (graceful degradation):", {
189
+ error: error instanceof Error ? error.message : String(error),
190
+ });
191
+ }
192
+ }
193
+ else {
194
+ this.logger.info("ℹ️ Auto-preload conditions not met", {
195
+ preloadIframe: this.config.preloadIframe,
196
+ displayMode: this.config.displayMode,
197
+ reason: !this.config.preloadIframe
198
+ ? "preloadIframe is disabled"
199
+ : "displayMode is not iframe",
200
+ });
201
+ }
151
202
  }
152
203
  catch (error) {
153
204
  const errorMessage = error instanceof Error
@@ -183,11 +234,63 @@ export class CivicAuth {
183
234
  ...handlerConfig,
184
235
  messageHandler: this.messageHandler.handleMessage,
185
236
  });
237
+ // Set up automatic re-preloading when authentication is cancelled
238
+ this.setupAutoRepreload();
239
+ }
240
+ /**
241
+ * Set up automatic re-preloading when authentication is cancelled by user
242
+ * This maintains instant sign-in experience for subsequent attempts
243
+ */
244
+ setupAutoRepreload() {
245
+ this.events.on(AuthEvent.SIGN_IN_ERROR, (event) => {
246
+ // Only re-preload for iframe mode with user cancellation and preload enabled
247
+ if (this.config.displayMode === "iframe" &&
248
+ this.config.preloadIframe &&
249
+ event?.detail === "Authentication cancelled by user") {
250
+ this.logger.debug("🔄 Authentication cancelled, scheduling re-preload for instant subsequent sign-in");
251
+ // Small delay to ensure cleanup is complete
252
+ setTimeout(async () => {
253
+ try {
254
+ await this.preloadAuthentication();
255
+ this.logger.debug("✅ Re-preloaded iframe after cancellation for instant sign-in");
256
+ }
257
+ catch (error) {
258
+ // Don't fail silently, but don't crash either - just log the issue
259
+ this.logger.warn("⚠️ Failed to re-preload iframe after cancellation:", {
260
+ error: error instanceof Error ? error.message : String(error),
261
+ });
262
+ }
263
+ }, 200); // Small delay to ensure cleanup completion
264
+ }
265
+ });
186
266
  }
187
267
  /**
188
268
  * Builds the authentication URL with PKCE challenge
189
269
  */
190
270
  async buildAuthUrl() {
271
+ // If a login URL is set (for backend integration), use that instead
272
+ if (this.loginUrl) {
273
+ this.logger.info("🔗 Using login URL for backend integration", {
274
+ loginUrl: this.loginUrl,
275
+ });
276
+ // Generate state with display mode information for backend integration
277
+ const state = this.config.initialState ||
278
+ generateState({
279
+ displayMode: this.config.displayMode || "iframe",
280
+ iframeDisplayMode: this.config.iframeDisplayMode,
281
+ framework: this.config.framework || "vanillajs",
282
+ sdkVersion: getVersion(),
283
+ });
284
+ // Append state as query parameter to loginUrl
285
+ const url = new URL(this.loginUrl, window.location.origin);
286
+ url.searchParams.set("state", state);
287
+ this.logger.info("🔗 Built login URL with display mode state", {
288
+ loginUrl: url.toString(),
289
+ displayMode: this.config.displayMode,
290
+ iframeDisplayMode: this.config.iframeDisplayMode,
291
+ });
292
+ return url.toString();
293
+ }
191
294
  if (!this.endpoints) {
192
295
  throw new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
193
296
  }
@@ -196,6 +299,7 @@ export class CivicAuth {
196
299
  const state = this.config.initialState ||
197
300
  generateState({
198
301
  displayMode: this.config.displayMode || "iframe",
302
+ iframeDisplayMode: this.config.iframeDisplayMode,
199
303
  framework: this.config.framework || "vanillajs",
200
304
  sdkVersion: getVersion(),
201
305
  });
@@ -210,6 +314,102 @@ export class CivicAuth {
210
314
  nonce: this.config.nonce,
211
315
  });
212
316
  }
317
+ /**
318
+ * Preloads the authentication iframe for instant sign-in
319
+ * This creates the iframe in the background but keeps it hidden until startAuthentication() is called
320
+ * @throws {CivicAuthError} If preloading fails or is not supported
321
+ * @private - This method is used internally for automatic preloading
322
+ */
323
+ async preloadAuthentication() {
324
+ this.logger.info("🎬 Preloading authentication for instant sign-in", {
325
+ displayMode: this.config.displayMode,
326
+ userAgent: navigator.userAgent,
327
+ currentUrl: window.location.href,
328
+ });
329
+ if (!this.endpoints) {
330
+ const error = new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
331
+ this.logger.error("❌ Endpoints not initialized for preload", {
332
+ error: error.message,
333
+ });
334
+ throw error;
335
+ }
336
+ // Only support preloading for iframe mode
337
+ if (this.config.displayMode !== "iframe") {
338
+ this.logger.warn("⚠️ Iframe preloading only supported for iframe display mode", {
339
+ displayMode: this.config.displayMode,
340
+ });
341
+ return;
342
+ }
343
+ if (!this.iframeAuthHandler) {
344
+ const error = new CivicAuthError("Iframe handler not initialized for preloading", CivicAuthErrorCode.INIT_FAILED);
345
+ this.logger.error("❌ Iframe handler not initialized", {
346
+ error: error.message,
347
+ });
348
+ throw error;
349
+ }
350
+ const fullAuthUrl = await this.buildAuthUrl();
351
+ this.logger.info("🔗 Built authentication URL for preload", {
352
+ url: fullAuthUrl,
353
+ displayMode: this.config.displayMode,
354
+ });
355
+ try {
356
+ await this.iframeAuthHandler.preloadIframe(fullAuthUrl);
357
+ const iframeElement = this.iframeAuthHandler.getIframeElement();
358
+ if (iframeElement) {
359
+ this.messageHandler?.updateIframeElement(iframeElement);
360
+ }
361
+ this.logger.info("✅ Authentication iframe preloaded successfully");
362
+ }
363
+ catch (error) {
364
+ const errorMessage = error instanceof Error ? error.message : "Failed to preload iframe";
365
+ this.logger.error("❌ Failed to preload authentication iframe", {
366
+ error: errorMessage,
367
+ });
368
+ throw new CivicAuthError(errorMessage, CivicAuthErrorCode.IFRAME_PRELOAD_FAILED);
369
+ }
370
+ }
371
+ /**
372
+ * Check if authentication is preloaded and ready for instant sign-in
373
+ * @returns True if an iframe is preloaded and ready
374
+ */
375
+ isAuthenticationPreloaded() {
376
+ if (this.config.displayMode !== "iframe" || !this.iframeAuthHandler) {
377
+ return false;
378
+ }
379
+ return this.iframeAuthHandler.hasPreloadedIframe();
380
+ }
381
+ /**
382
+ * Enable or disable iframe preloading
383
+ * @param enabled Whether to enable iframe preloading
384
+ */
385
+ setPreloadEnabled(enabled) {
386
+ // Preloading only makes sense for iframe mode
387
+ if (this.config.displayMode !== "iframe") {
388
+ this.logger.debug("🎯 Iframe preloading not applicable for non-iframe mode", {
389
+ displayMode: this.config.displayMode,
390
+ enabled,
391
+ });
392
+ return;
393
+ }
394
+ if (this.iframeAuthHandler) {
395
+ this.iframeAuthHandler.setPreloadEnabled(enabled);
396
+ this.logger.info("🎯 Iframe preloading", { enabled });
397
+ }
398
+ else {
399
+ this.logger.warn("⚠️ Cannot set preload enabled: iframe handler not initialized");
400
+ }
401
+ }
402
+ /**
403
+ * Check if iframe preloading is enabled
404
+ * @returns True if iframe preloading is enabled
405
+ */
406
+ getPreloadEnabled() {
407
+ // Preloading only makes sense for iframe mode
408
+ if (this.config.displayMode !== "iframe") {
409
+ return false;
410
+ }
411
+ return this.iframeAuthHandler?.getPreloadEnabled() ?? false;
412
+ }
213
413
  /**
214
414
  * Starts the authentication process
215
415
  * @returns A promise that resolves with the authentication result
@@ -252,6 +452,25 @@ export class CivicAuth {
252
452
  });
253
453
  return this.authPromise;
254
454
  }
455
+ async handleBrowserCorsFailsSilently() {
456
+ if (!this.iframeAuthHandler || !this.messageHandler) {
457
+ throw new Error("Iframe handler not initialized");
458
+ }
459
+ const rebuiltUrl = await this.buildAuthUrl();
460
+ this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
461
+ detail: "Browser doesn't support popups and passkey with CORS in iframe - switching to redirect mode",
462
+ error: {
463
+ type: "browser_cors_fails_silently",
464
+ failedUrl: IframeManager.browserCorsFailsSilentlyUrl,
465
+ suggestion: "Browser doesn't support popups and passkey with CORS in iframe - switching to redirect mode",
466
+ },
467
+ });
468
+ this.iframeAuthHandler.forceShowLoader();
469
+ // use a small delay to ensure the loader is shown
470
+ setTimeout(() => {
471
+ window.location.href = rebuiltUrl;
472
+ }, 100);
473
+ }
255
474
  /**
256
475
  * Handle authentication based on display mode
257
476
  */
@@ -278,6 +497,9 @@ export class CivicAuth {
278
497
  if (!this.iframeAuthHandler || !this.messageHandler) {
279
498
  throw new Error("Iframe handler not initialized");
280
499
  }
500
+ if (IframeManager.browserCorsFailsSilentlyUrl) {
501
+ return this.handleBrowserCorsFailsSilently();
502
+ }
281
503
  const iframeElement = await this.iframeAuthHandler.handleIframeAuth(fullAuthUrl);
282
504
  this.messageHandler.updateIframeElement(iframeElement);
283
505
  break;
@@ -341,19 +563,15 @@ export class CivicAuth {
341
563
  });
342
564
  return;
343
565
  }
566
+ // Set up timeout for other modes
344
567
  if (this.config.authProcessTimeout && this.config.authProcessTimeout > 0) {
345
568
  this.logger.debug("⏰ Setting up authentication timeout", {
346
- authProcessTimeout: this.config.authProcessTimeout,
569
+ timeout: this.config.authProcessTimeout,
347
570
  displayMode: this.config.displayMode,
348
571
  iframeDisplayMode: this.config.iframeDisplayMode,
349
572
  });
350
573
  this.authProcessTimeoutHandle = window.setTimeout(() => {
351
- this.logger.error("⏰ Authentication timed out", {
352
- displayMode: this.config.displayMode,
353
- iframeDisplayMode: this.config.iframeDisplayMode,
354
- currentOrigin: window.location.origin,
355
- authProcessTimeout: this.config.authProcessTimeout,
356
- });
574
+ this.logger.info("⏰ Authentication timeout reached");
357
575
  this.events.emit(AuthEvent.SIGN_IN_ERROR, {
358
576
  detail: "Authentication timed out",
359
577
  });
@@ -599,6 +817,54 @@ export class CivicAuth {
599
817
  getTokenRefresherState() {
600
818
  return this.sessionManager.getTokenRefresherState() || null;
601
819
  }
820
+ /**
821
+ * Set a custom login URL for backend integration.
822
+ * This is useful when integrating with a backend that handles the OAuth flow.
823
+ * Alternatively, you can configure this directly in CivicAuth.create().
824
+ *
825
+ * @param loginUrl - The custom login URL to use (e.g., "http://localhost:3020/auth/login")
826
+ *
827
+ * @example
828
+ * ```typescript
829
+ * // Option 1: Configure in create()
830
+ * const authClient = await CivicAuth.create({
831
+ * clientId: "YOUR_CLIENT_ID",
832
+ * loginUrl: "http://example.com/custom-backendurl"
833
+ * });
834
+ *
835
+ * // Option 2: Set after creation
836
+ * civicAuth.setLoginUrl("http://localhost:3020/auth/login");
837
+ * await civicAuth.startAuthentication();
838
+ * ```
839
+ */
840
+ setLoginUrl(loginUrl) {
841
+ this.loginUrl = loginUrl;
842
+ // Update MessageHandler to expect messages from the custom origin
843
+ if (this.messageHandler) {
844
+ this.messageHandler.setCustomExpectedOrigin(loginUrl);
845
+ }
846
+ this.logger.info("🔗 Custom login URL set for backend integration", {
847
+ loginUrl: this.loginUrl,
848
+ });
849
+ }
850
+ /**
851
+ * Clear the login URL and return to standard OAuth flow
852
+ */
853
+ clearLoginUrl() {
854
+ this.loginUrl = undefined;
855
+ // Clear custom expected origin from MessageHandler
856
+ if (this.messageHandler) {
857
+ this.messageHandler.clearCustomExpectedOrigin();
858
+ }
859
+ this.logger.info("🔗 Login URL cleared, returning to standard OAuth flow");
860
+ }
861
+ /**
862
+ * Get the current login URL
863
+ * @returns The current login URL or undefined if not set
864
+ */
865
+ getLoginUrl() {
866
+ return this.loginUrl;
867
+ }
602
868
  /**
603
869
  * Update the iframe display mode
604
870
  * @param mode - The display mode to use for the iframe
@@ -631,7 +897,27 @@ export class CivicAuth {
631
897
  this.events.emit(AuthEvent.SIGN_OUT_STARTED, {
632
898
  detail: "Logout process started",
633
899
  });
634
- // Get current tokens before clearing them
900
+ // If a loginUrl is configured, redirect to the backend's logout endpoint.
901
+ // The backend will then handle redirecting to the OIDC provider.
902
+ if (this.loginUrl) {
903
+ this.logger.info("🚪 Redirecting to backend for logout in backend-integrated flow");
904
+ const backendUrl = new URL(this.loginUrl).origin;
905
+ const endpoints = getBackendEndpoints(this.config.backendEndpoints);
906
+ const backendLogoutUrl = `${backendUrl}${endpoints.logout}`;
907
+ // Clear local SDK session state before redirecting
908
+ await this.sessionManager.clearSession();
909
+ this.events.emit(AuthEvent.SIGN_OUT_COMPLETE, {
910
+ detail: "Local session cleared, redirecting to backend for logout.",
911
+ });
912
+ // Perform top-level redirect to the backend logout endpoint
913
+ this.logger.info("🌐 Redirecting to backend logout endpoint", {
914
+ url: backendLogoutUrl,
915
+ });
916
+ window.location.href = backendLogoutUrl;
917
+ return;
918
+ }
919
+ // --- Existing SPA redirect-based logout flow ---
920
+ this.logger.info("🚪 Using redirect-based logout for SPA flow");
635
921
  const tokens = await retrieveTokens(this.storage);
636
922
  if (!tokens?.id_token) {
637
923
  this.logger.warn("⚠️ No ID token found, clearing local session only");