@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
@@ -0,0 +1,41 @@
1
+ import type { AuthConfig } from "../../server/config.js";
2
+ import type { OIDCTokenResponseBody } from "../../types.js";
3
+ import { GenericAuthenticationRefresher } from "../../shared/lib/GenericAuthenticationRefresher.js";
4
+ import type { AuthenticationEvents } from "./AuthenticationEvents.js";
5
+ /**
6
+ * BackendAuthenticationRefresher handles token refresh for backend authentication flows
7
+ * by calling the backend's refresh API endpoint instead of accessing browser storage.
8
+ * This is used when loginUrl is configured, indicating backend integration.
9
+ */
10
+ export declare class BackendAuthenticationRefresher extends GenericAuthenticationRefresher {
11
+ private logger;
12
+ private loginUrl;
13
+ private autoRefreshTimeoutId?;
14
+ private events?;
15
+ constructor(authConfig: AuthConfig, loginUrl: string, onError: (error: Error) => Promise<void>, events?: AuthenticationEvents);
16
+ static build(authConfig: AuthConfig, loginUrl: string, onError: (error: Error) => Promise<void>, events?: AuthenticationEvents): Promise<BackendAuthenticationRefresher>;
17
+ /**
18
+ * Override getRefreshToken to indicate that backend flows don't need browser-accessible refresh tokens
19
+ */
20
+ getRefreshToken(): Promise<string>;
21
+ /**
22
+ * Refresh tokens by calling the backend's refresh API endpoint
23
+ */
24
+ refreshAccessToken(): Promise<OIDCTokenResponseBody | null>;
25
+ /**
26
+ * For backend flows, we don't need to store tokens in browser storage
27
+ * since they're managed server-side in HTTP-only cookies
28
+ */
29
+ storeTokens(tokenResponseBody: OIDCTokenResponseBody | null): Promise<void>;
30
+ /**
31
+ * Setup auto-refresh for backend flows
32
+ * Since we can't access token expiration from HTTP-only cookies,
33
+ * we'll use a conservative refresh interval
34
+ */
35
+ setupAutorefresh(): Promise<void>;
36
+ /**
37
+ * Clear auto-refresh for backend flows
38
+ */
39
+ clearAutorefresh(): void;
40
+ }
41
+ //# sourceMappingURL=BackendAuthenticationRefresher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackendAuthenticationRefresher.d.ts","sourceRoot":"","sources":["../../../src/vanillajs/auth/BackendAuthenticationRefresher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,8BAA8B,EAAE,MAAM,oDAAoD,CAAC;AAGpG,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAGtE;;;;GAIG;AACH,qBAAa,8BAA+B,SAAQ,8BAA8B;IAChF,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,OAAO,CAAC,MAAM,CAAC,CAAuB;gBAGpC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,EACxC,MAAM,CAAC,EAAE,oBAAoB;WAWlB,KAAK,CAChB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,EACxC,MAAM,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,8BAA8B,CAAC;IAS1C;;OAEG;IACY,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAOjD;;OAEG;IACY,kBAAkB,IAAI,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;IAuD1E;;;OAGG;IACG,WAAW,CACf,iBAAiB,EAAE,qBAAqB,GAAG,IAAI,GAC9C,OAAO,CAAC,IAAI,CAAC;IAQhB;;;;OAIG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IA2BvC;;OAEG;IACH,gBAAgB,IAAI,IAAI;CAOzB"}
@@ -0,0 +1,125 @@
1
+ import { GenericAuthenticationRefresher } from "../../shared/lib/GenericAuthenticationRefresher.js";
2
+ import { getBackendEndpoints } from "../../shared/lib/util.js";
3
+ import { createLogger } from "../utils/logger.js";
4
+ import { AuthEvent } from "../types/index.js";
5
+ /**
6
+ * BackendAuthenticationRefresher handles token refresh for backend authentication flows
7
+ * by calling the backend's refresh API endpoint instead of accessing browser storage.
8
+ * This is used when loginUrl is configured, indicating backend integration.
9
+ */
10
+ export class BackendAuthenticationRefresher extends GenericAuthenticationRefresher {
11
+ logger = createLogger("backend-auth-refresher");
12
+ loginUrl;
13
+ autoRefreshTimeoutId;
14
+ events;
15
+ constructor(authConfig, loginUrl, onError, events) {
16
+ super(onError);
17
+ this.authConfig = authConfig;
18
+ this.loginUrl = loginUrl;
19
+ this.events = events;
20
+ this.logger.info("BackendAuthenticationRefresher initialized", {
21
+ loginUrl: this.loginUrl,
22
+ });
23
+ }
24
+ static async build(authConfig, loginUrl, onError, events) {
25
+ return new BackendAuthenticationRefresher(authConfig, loginUrl, onError, events);
26
+ }
27
+ /**
28
+ * Override getRefreshToken to indicate that backend flows don't need browser-accessible refresh tokens
29
+ */
30
+ async getRefreshToken() {
31
+ // For backend flows, we don't need to retrieve the refresh token from browser storage
32
+ // The backend handles the refresh token internally via HTTP-only cookies
33
+ this.logger.debug("Backend flow: refresh token managed server-side");
34
+ return "backend-managed"; // Placeholder token
35
+ }
36
+ /**
37
+ * Refresh tokens by calling the backend's refresh API endpoint
38
+ */
39
+ async refreshAccessToken() {
40
+ try {
41
+ // Emit refresh started event
42
+ this.events?.emit(AuthEvent.TOKEN_REFRESH_STARTED, null);
43
+ const backendUrl = new URL(this.loginUrl).origin;
44
+ const endpoints = getBackendEndpoints(this.authConfig?.backendEndpoints);
45
+ const refreshEndpoint = `${backendUrl}${endpoints.refresh}`;
46
+ this.logger.info("Calling backend refresh endpoint", {
47
+ endpoint: refreshEndpoint,
48
+ });
49
+ const response = await fetch(refreshEndpoint, {
50
+ method: "POST",
51
+ credentials: "include", // Include HTTP-only cookies
52
+ headers: {
53
+ "Content-Type": "application/json",
54
+ },
55
+ });
56
+ if (!response.ok) {
57
+ const errorText = await response.text().catch(() => "Unknown error");
58
+ const error = new Error(`Backend refresh failed: ${response.status} ${response.statusText} - ${errorText}`);
59
+ // Emit refresh error event
60
+ this.events?.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);
61
+ throw error;
62
+ }
63
+ this.logger.info("Backend token refresh successful");
64
+ // Emit refresh complete event
65
+ this.events?.emit(AuthEvent.TOKEN_REFRESH_COMPLETE, null);
66
+ // For backend flows, tokens are managed in HTTP-only cookies
67
+ // and are not accessible to JavaScript, so we return null
68
+ return null;
69
+ }
70
+ catch (error) {
71
+ this.logger.error("Backend token refresh failed", { error });
72
+ // Emit refresh error event if not already emitted
73
+ if (error instanceof Error &&
74
+ !error.message.includes("Backend refresh failed")) {
75
+ this.events?.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);
76
+ }
77
+ throw error;
78
+ }
79
+ }
80
+ /**
81
+ * For backend flows, we don't need to store tokens in browser storage
82
+ * since they're managed server-side in HTTP-only cookies
83
+ */
84
+ async storeTokens(tokenResponseBody) {
85
+ this.logger.debug("Backend flow: tokens stored server-side, skipping browser storage", { tokenResponseBody });
86
+ // No-op for backend flows - tokens are stored server-side
87
+ }
88
+ /**
89
+ * Setup auto-refresh for backend flows
90
+ * Since we can't access token expiration from HTTP-only cookies,
91
+ * we'll use a conservative refresh interval
92
+ */
93
+ async setupAutorefresh() {
94
+ this.logger.info("Setting up auto-refresh for backend flow");
95
+ // Clear any existing timeout
96
+ this.clearAutorefresh();
97
+ // For backend flows, we can't read token expiration from HTTP-only cookies
98
+ // So we'll use a conservative refresh interval (e.g., every 50 minutes for 1-hour tokens)
99
+ const refreshIntervalMs = 50 * 60 * 1000; // 50 minutes
100
+ this.autoRefreshTimeoutId = window.setTimeout(async () => {
101
+ try {
102
+ this.logger.info("Auto-refreshing backend tokens");
103
+ await this.refreshTokens();
104
+ // Schedule next refresh
105
+ this.setupAutorefresh();
106
+ }
107
+ catch (error) {
108
+ this.logger.error("Auto-refresh failed", { error });
109
+ await this.onError(error);
110
+ }
111
+ }, refreshIntervalMs);
112
+ this.logger.info(`Next backend token refresh scheduled in ${refreshIntervalMs / (60 * 1000)} minutes`);
113
+ }
114
+ /**
115
+ * Clear auto-refresh for backend flows
116
+ */
117
+ clearAutorefresh() {
118
+ if (this.autoRefreshTimeoutId) {
119
+ this.logger.debug("Clearing auto-refresh timeout for backend flow");
120
+ window.clearTimeout(this.autoRefreshTimeoutId);
121
+ this.autoRefreshTimeoutId = undefined;
122
+ }
123
+ }
124
+ }
125
+ //# sourceMappingURL=BackendAuthenticationRefresher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackendAuthenticationRefresher.js","sourceRoot":"","sources":["../../../src/vanillajs/auth/BackendAuthenticationRefresher.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthConfig } from \"../../server/config.js\";\nimport type { OIDCTokenResponseBody } from \"../../types.js\";\nimport { GenericAuthenticationRefresher } from \"../../shared/lib/GenericAuthenticationRefresher.js\";\nimport { getBackendEndpoints } from \"../../shared/lib/util.js\";\nimport { createLogger } from \"../utils/logger.js\";\nimport type { AuthenticationEvents } from \"./AuthenticationEvents.js\";\nimport { AuthEvent } from \"../types/index.js\";\n\n/**\n * BackendAuthenticationRefresher handles token refresh for backend authentication flows\n * by calling the backend's refresh API endpoint instead of accessing browser storage.\n * This is used when loginUrl is configured, indicating backend integration.\n */\nexport class BackendAuthenticationRefresher extends GenericAuthenticationRefresher {\n private logger = createLogger(\"backend-auth-refresher\");\n private loginUrl: string;\n private autoRefreshTimeoutId?: number;\n private events?: AuthenticationEvents;\n\n constructor(\n authConfig: AuthConfig,\n loginUrl: string,\n onError: (error: Error) => Promise<void>,\n events?: AuthenticationEvents,\n ) {\n super(onError);\n this.authConfig = authConfig;\n this.loginUrl = loginUrl;\n this.events = events;\n this.logger.info(\"BackendAuthenticationRefresher initialized\", {\n loginUrl: this.loginUrl,\n });\n }\n\n static async build(\n authConfig: AuthConfig,\n loginUrl: string,\n onError: (error: Error) => Promise<void>,\n events?: AuthenticationEvents,\n ): Promise<BackendAuthenticationRefresher> {\n return new BackendAuthenticationRefresher(\n authConfig,\n loginUrl,\n onError,\n events,\n );\n }\n\n /**\n * Override getRefreshToken to indicate that backend flows don't need browser-accessible refresh tokens\n */\n override async getRefreshToken(): Promise<string> {\n // For backend flows, we don't need to retrieve the refresh token from browser storage\n // The backend handles the refresh token internally via HTTP-only cookies\n this.logger.debug(\"Backend flow: refresh token managed server-side\");\n return \"backend-managed\"; // Placeholder token\n }\n\n /**\n * Refresh tokens by calling the backend's refresh API endpoint\n */\n override async refreshAccessToken(): Promise<OIDCTokenResponseBody | null> {\n try {\n // Emit refresh started event\n this.events?.emit(AuthEvent.TOKEN_REFRESH_STARTED, null);\n\n const backendUrl = new URL(this.loginUrl).origin;\n const endpoints = getBackendEndpoints(this.authConfig?.backendEndpoints);\n const refreshEndpoint = `${backendUrl}${endpoints.refresh}`;\n\n this.logger.info(\"Calling backend refresh endpoint\", {\n endpoint: refreshEndpoint,\n });\n\n const response = await fetch(refreshEndpoint, {\n method: \"POST\",\n credentials: \"include\", // Include HTTP-only cookies\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n const error = new Error(\n `Backend refresh failed: ${response.status} ${response.statusText} - ${errorText}`,\n );\n\n // Emit refresh error event\n this.events?.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n throw error;\n }\n\n this.logger.info(\"Backend token refresh successful\");\n\n // Emit refresh complete event\n this.events?.emit(AuthEvent.TOKEN_REFRESH_COMPLETE, null);\n\n // For backend flows, tokens are managed in HTTP-only cookies\n // and are not accessible to JavaScript, so we return null\n return null;\n } catch (error) {\n this.logger.error(\"Backend token refresh failed\", { error });\n\n // Emit refresh error event if not already emitted\n if (\n error instanceof Error &&\n !error.message.includes(\"Backend refresh failed\")\n ) {\n this.events?.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n }\n\n throw error;\n }\n }\n\n /**\n * For backend flows, we don't need to store tokens in browser storage\n * since they're managed server-side in HTTP-only cookies\n */\n async storeTokens(\n tokenResponseBody: OIDCTokenResponseBody | null,\n ): Promise<void> {\n this.logger.debug(\n \"Backend flow: tokens stored server-side, skipping browser storage\",\n { tokenResponseBody },\n );\n // No-op for backend flows - tokens are stored server-side\n }\n\n /**\n * Setup auto-refresh for backend flows\n * Since we can't access token expiration from HTTP-only cookies,\n * we'll use a conservative refresh interval\n */\n async setupAutorefresh(): Promise<void> {\n this.logger.info(\"Setting up auto-refresh for backend flow\");\n\n // Clear any existing timeout\n this.clearAutorefresh();\n\n // For backend flows, we can't read token expiration from HTTP-only cookies\n // So we'll use a conservative refresh interval (e.g., every 50 minutes for 1-hour tokens)\n const refreshIntervalMs = 50 * 60 * 1000; // 50 minutes\n\n this.autoRefreshTimeoutId = window.setTimeout(async () => {\n try {\n this.logger.info(\"Auto-refreshing backend tokens\");\n await this.refreshTokens();\n // Schedule next refresh\n this.setupAutorefresh();\n } catch (error) {\n this.logger.error(\"Auto-refresh failed\", { error });\n await this.onError(error as Error);\n }\n }, refreshIntervalMs);\n\n this.logger.info(\n `Next backend token refresh scheduled in ${refreshIntervalMs / (60 * 1000)} minutes`,\n );\n }\n\n /**\n * Clear auto-refresh for backend flows\n */\n clearAutorefresh(): void {\n if (this.autoRefreshTimeoutId) {\n this.logger.debug(\"Clearing auto-refresh timeout for backend flow\");\n window.clearTimeout(this.autoRefreshTimeoutId);\n this.autoRefreshTimeoutId = undefined;\n }\n }\n}\n"]}
@@ -19,6 +19,7 @@ export declare class CivicAuth {
19
19
  private authProcessTimeoutHandle?;
20
20
  private popupFailureTimeoutHandle?;
21
21
  private hasPopupFailed;
22
+ private loginUrl?;
22
23
  private messageHandler?;
23
24
  private popupHandler?;
24
25
  private iframeAuthHandler?;
@@ -37,6 +38,7 @@ export declare class CivicAuth {
37
38
  *
38
39
  * @example
39
40
  * ```typescript
41
+ * // Standard SPA authentication
40
42
  * const auth = await CivicAuth.create({
41
43
  * clientId: "your-client-id",
42
44
  * // redirectUrl is optional - defaults to current page (window.location.origin + window.location.pathname)
@@ -52,6 +54,12 @@ export declare class CivicAuth {
52
54
  * success: "Authentication successful!"
53
55
  * }
54
56
  * });
57
+ *
58
+ * // Backend integration authentication
59
+ * const authWithBackend = await CivicAuth.create({
60
+ * clientId: "your-client-id",
61
+ * loginUrl: "http://example.com/custom-backendurl" // Automatically uses BrowserCookieStorage
62
+ * });
55
63
  * ```
56
64
  */
57
65
  static create(config: CivicAuthClientConfig): Promise<CivicAuth>;
@@ -63,16 +71,44 @@ export declare class CivicAuth {
63
71
  * Initialize all handlers with proper configuration
64
72
  */
65
73
  private initializeHandlers;
74
+ /**
75
+ * Set up automatic re-preloading when authentication is cancelled by user
76
+ * This maintains instant sign-in experience for subsequent attempts
77
+ */
78
+ private setupAutoRepreload;
66
79
  /**
67
80
  * Builds the authentication URL with PKCE challenge
68
81
  */
69
82
  private buildAuthUrl;
83
+ /**
84
+ * Preloads the authentication iframe for instant sign-in
85
+ * This creates the iframe in the background but keeps it hidden until startAuthentication() is called
86
+ * @throws {CivicAuthError} If preloading fails or is not supported
87
+ * @private - This method is used internally for automatic preloading
88
+ */
89
+ private preloadAuthentication;
90
+ /**
91
+ * Check if authentication is preloaded and ready for instant sign-in
92
+ * @returns True if an iframe is preloaded and ready
93
+ */
94
+ isAuthenticationPreloaded(): boolean;
95
+ /**
96
+ * Enable or disable iframe preloading
97
+ * @param enabled Whether to enable iframe preloading
98
+ */
99
+ setPreloadEnabled(enabled: boolean): void;
100
+ /**
101
+ * Check if iframe preloading is enabled
102
+ * @returns True if iframe preloading is enabled
103
+ */
104
+ getPreloadEnabled(): boolean;
70
105
  /**
71
106
  * Starts the authentication process
72
107
  * @returns A promise that resolves with the authentication result
73
108
  * @throws {CivicAuthError} If authentication fails or times out
74
109
  */
75
110
  startAuthentication(): Promise<AuthResult>;
111
+ private handleBrowserCorsFailsSilently;
76
112
  /**
77
113
  * Handle authentication based on display mode
78
114
  */
@@ -150,6 +186,36 @@ export declare class CivicAuth {
150
186
  isAuthenticated: boolean;
151
187
  isAutoRefreshActive: boolean;
152
188
  } | null;
189
+ /**
190
+ * Set a custom login URL for backend integration.
191
+ * This is useful when integrating with a backend that handles the OAuth flow.
192
+ * Alternatively, you can configure this directly in CivicAuth.create().
193
+ *
194
+ * @param loginUrl - The custom login URL to use (e.g., "http://localhost:3020/auth/login")
195
+ *
196
+ * @example
197
+ * ```typescript
198
+ * // Option 1: Configure in create()
199
+ * const authClient = await CivicAuth.create({
200
+ * clientId: "YOUR_CLIENT_ID",
201
+ * loginUrl: "http://example.com/custom-backendurl"
202
+ * });
203
+ *
204
+ * // Option 2: Set after creation
205
+ * civicAuth.setLoginUrl("http://localhost:3020/auth/login");
206
+ * await civicAuth.startAuthentication();
207
+ * ```
208
+ */
209
+ setLoginUrl(loginUrl: string): void;
210
+ /**
211
+ * Clear the login URL and return to standard OAuth flow
212
+ */
213
+ clearLoginUrl(): void;
214
+ /**
215
+ * Get the current login URL
216
+ * @returns The current login URL or undefined if not set
217
+ */
218
+ getLoginUrl(): string | undefined;
153
219
  /**
154
220
  * Update the iframe display mode
155
221
  * @param mode - The display mode to use for the iframe
@@ -1 +1 @@
1
- {"version":3,"file":"CivicAuth.d.ts","sourceRoot":"","sources":["../../../src/vanillajs/auth/CivicAuth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAwB7D,OAAO,KAAK,EACV,qBAAqB,EAEtB,MAAM,sBAAsB,CAAC;AAW9B;;;;GAIG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,SAAS,CAAC,CAAY;IAC9B,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,kBAAkB,CAKT;IAGjB,OAAO,CAAC,WAAW,CAAC,CAAsB;IAC1C,OAAO,CAAC,kBAAkB,CAAC,CAA8B;IACzD,OAAO,CAAC,iBAAiB,CAAC,CAA2B;IACrD,OAAO,CAAC,wBAAwB,CAAC,CAAS;IAC1C,OAAO,CAAC,yBAAyB,CAAC,CAAS;IAC3C,OAAO,CAAC,cAAc,CAAkB;IAGxC,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,YAAY,CAAC,CAAe;IACpC,OAAO,CAAC,iBAAiB,CAAC,CAAoB;IAE9C;;;OAGG;IACH,OAAO;IAiCP;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;WACiB,MAAM,CACxB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,SAAS,CAAC;IAMrB;;OAEG;YACW,IAAI;IAgElB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAsB1B;;OAEG;YACW,YAAY;IA8B1B;;;;OAIG;IACG,mBAAmB,IAAI,OAAO,CAAC,UAAU,CAAC;IAsDhD;;OAEG;YACW,iCAAiC;IA6C/C;;OAEG;YACW,4BAA4B;IA6C1C;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA4ClC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAMvB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAiC1B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IA6C/B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAehC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAe3B;;OAEG;YACW,cAAc;IA6D5B;;OAEG;IACI,OAAO,IAAI,IAAI;IA6BtB;;OAEG;IACU,iBAAiB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAIzD;;;OAGG;IACU,4BAA4B;IAKzC;;OAEG;IACU,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAIhD;;OAEG;IACU,cAAc;IAI3B;;OAEG;IACU,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAI1C;;OAEG;IACU,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3C;;OAEG;IACI,sBAAsB;;;;;IAI7B;;;OAGG;IACI,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI;IAK7D;;;OAGG;IACI,oBAAoB,IAAI,OAAO,GAAG,UAAU,GAAG,SAAS;IAI/D;;OAEG;IACU,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrC;;OAEG;IACU,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IA6EpC;;;OAGG;YACW,wBAAwB;CAsEvC;AAGD,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"CivicAuth.d.ts","sourceRoot":"","sources":["../../../src/vanillajs/auth/CivicAuth.ts"],"names":[],"mappings":"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"}