oidc-spa 8.2.2 → 8.2.3
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/core/createOidc.d.ts +48 -16
- package/core/createOidc.js +22 -11
- package/core/createOidc.js.map +1 -1
- package/core/desiredPostLoginRedirectUrl.d.ts +4 -0
- package/core/desiredPostLoginRedirectUrl.js +12 -0
- package/core/desiredPostLoginRedirectUrl.js.map +1 -0
- package/core/diagnostic.d.ts +1 -1
- package/core/diagnostic.js +3 -3
- package/core/diagnostic.js.map +1 -1
- package/esm/angular.d.ts +27 -4
- package/esm/angular.js +28 -6
- package/esm/angular.js.map +1 -1
- package/esm/core/createOidc.d.ts +48 -16
- package/esm/core/createOidc.js +22 -11
- package/esm/core/createOidc.js.map +1 -1
- package/esm/core/desiredPostLoginRedirectUrl.d.ts +4 -0
- package/esm/core/desiredPostLoginRedirectUrl.js +8 -0
- package/esm/core/desiredPostLoginRedirectUrl.js.map +1 -0
- package/esm/core/diagnostic.d.ts +1 -1
- package/esm/core/diagnostic.js +3 -3
- package/esm/core/diagnostic.js.map +1 -1
- package/esm/keycloak/keycloak-js/Keycloak.d.ts +40 -0
- package/esm/keycloak/keycloak-js/Keycloak.js +2 -1
- package/esm/keycloak/keycloak-js/Keycloak.js.map +1 -1
- package/esm/react/react.js +24 -2
- package/esm/react/react.js.map +1 -1
- package/esm/react-spa/createOidcSpaApi.js +26 -4
- package/esm/react-spa/createOidcSpaApi.js.map +1 -1
- package/esm/react-spa/types.d.ts +26 -3
- package/esm/tanstack-start/react/createOidcSpaApi.js +25 -3
- package/esm/tanstack-start/react/createOidcSpaApi.js.map +1 -1
- package/esm/tanstack-start/react/types.d.ts +26 -3
- package/esm/tools/lazySessionStorage.d.ts +3 -1
- package/esm/tools/lazySessionStorage.js +8 -6
- package/esm/tools/lazySessionStorage.js.map +1 -1
- package/keycloak/keycloak-js/Keycloak.d.ts +40 -0
- package/keycloak/keycloak-js/Keycloak.js +2 -1
- package/keycloak/keycloak-js/Keycloak.js.map +1 -1
- package/package.json +5 -1
- package/react/react.js +24 -2
- package/react/react.js.map +1 -1
- package/react-spa/createOidcSpaApi.js +26 -4
- package/react-spa/createOidcSpaApi.js.map +1 -1
- package/react-spa/types.d.ts +26 -3
- package/src/angular.ts +72 -18
- package/src/core/createOidc.ts +73 -27
- package/src/core/desiredPostLoginRedirectUrl.ts +9 -0
- package/src/core/diagnostic.ts +4 -4
- package/src/keycloak/keycloak-js/Keycloak.ts +43 -1
- package/src/react/react.tsx +32 -3
- package/src/react-spa/createOidcSpaApi.tsx +34 -5
- package/src/react-spa/types.tsx +26 -3
- package/src/tanstack-start/react/createOidcSpaApi.tsx +33 -4
- package/src/tanstack-start/react/types.tsx +26 -3
- package/src/tools/lazySessionStorage.ts +11 -7
- package/src/vite-plugin/manageOptimizedDeps.ts +2 -1
- package/tools/lazySessionStorage.d.ts +3 -1
- package/tools/lazySessionStorage.js +8 -6
- package/tools/lazySessionStorage.js.map +1 -1
- package/vite-plugin/manageOptimizedDeps.js +2 -1
- package/vite-plugin/manageOptimizedDeps.js.map +1 -1
package/src/core/createOidc.ts
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
evtIsThereMoreThanOneInstanceThatCantUserIframes,
|
|
53
53
|
notifyNewInstanceThatCantUseIframes
|
|
54
54
|
} from "./instancesThatCantUseIframes";
|
|
55
|
+
import { getDesiredPostLoginRedirectUrl } from "./desiredPostLoginRedirectUrl";
|
|
55
56
|
|
|
56
57
|
// NOTE: Replaced at build time
|
|
57
58
|
const VERSION = "{{OIDC_SPA_VERSION}}";
|
|
@@ -106,21 +107,6 @@ export type ParamsOfCreateOidc<
|
|
|
106
107
|
* extraTokenParams: { selectedCustomer: "xxx" }
|
|
107
108
|
*/
|
|
108
109
|
extraTokenParams?: Record<string, string | undefined> | (() => Record<string, string | undefined>);
|
|
109
|
-
/**
|
|
110
|
-
* @deprecated: Use login({ redirectUrl: "..." }) instead.
|
|
111
|
-
*
|
|
112
|
-
* Usage discouraged, it's here because we don't want to assume too much on your
|
|
113
|
-
* usecase but I can't think of a scenario where you would want anything
|
|
114
|
-
* other than the current page.
|
|
115
|
-
*
|
|
116
|
-
* Where to redirect after successful login.
|
|
117
|
-
* Default: window.location.href (here)
|
|
118
|
-
*
|
|
119
|
-
* It does not need to include the origin, eg: "/dashboard"
|
|
120
|
-
*
|
|
121
|
-
* This parameter can also be passed to login() directly as `redirectUrl`.
|
|
122
|
-
*/
|
|
123
|
-
postLoginRedirectUrl?: string;
|
|
124
110
|
|
|
125
111
|
decodedIdTokenSchema?: {
|
|
126
112
|
parse: (decodedIdToken_original: Oidc.Tokens.DecodedIdToken_OidcCoreSpec) => DecodedIdToken;
|
|
@@ -150,9 +136,43 @@ export type ParamsOfCreateOidc<
|
|
|
150
136
|
autoLogin?: AutoLogin;
|
|
151
137
|
|
|
152
138
|
/**
|
|
139
|
+
* Determines how session restoration is handled.
|
|
140
|
+
* Session restoration allows users to stay logged in between visits
|
|
141
|
+
* without needing to explicitly sign in each time.
|
|
142
|
+
*
|
|
143
|
+
* Options:
|
|
144
|
+
*
|
|
145
|
+
* - **"auto" (default)**:
|
|
146
|
+
* Automatically selects the best method.
|
|
147
|
+
* If the app’s domain shares a common parent domain with the authorization endpoint,
|
|
148
|
+
* an iframe is used for silent session restoration.
|
|
149
|
+
* Otherwise, a full-page redirect is used.
|
|
150
|
+
*
|
|
151
|
+
* - **"full page redirect"**:
|
|
152
|
+
* Forces full-page reloads for session restoration.
|
|
153
|
+
* Use this if your application is served with a restrictive CSP
|
|
154
|
+
* (e.g., `Content-Security-Policy: frame-ancestors "none"`)
|
|
155
|
+
* or `X-Frame-Options: DENY`, and you cannot modify those headers.
|
|
156
|
+
* This mode provides a slightly less seamless UX and will lead oidc-spa to
|
|
157
|
+
* store tokens in `localStorage` if multiple OIDC clients are used
|
|
158
|
+
* (e.g., your app communicates with several APIs).
|
|
159
|
+
*
|
|
160
|
+
* - **"iframe"**:
|
|
161
|
+
* Forces iframe-based session restoration.
|
|
162
|
+
* In development, if you go in your browser setting and allow your auth server’s domain
|
|
163
|
+
* to set third-party cookies this value will let you test your app
|
|
164
|
+
* with the local dev server as it will behave in production.
|
|
165
|
+
*
|
|
166
|
+
* See: https://docs.oidc-spa.dev/v/v8/resources/third-party-cookies-and-session-restoration
|
|
167
|
+
*/
|
|
168
|
+
sessionRestorationMethod?: "iframe" | "full page redirect" | "auto";
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @deprecated Use `sessionRestorationMethod: "full page redirect"` instead.
|
|
172
|
+
*
|
|
153
173
|
* Default: false
|
|
154
174
|
*
|
|
155
|
-
* See: https://docs.oidc-spa.dev/v/v8/resources/
|
|
175
|
+
* See: https://docs.oidc-spa.dev/v/v8/resources/third-party-cookies-and-session-restoration
|
|
156
176
|
*/
|
|
157
177
|
noIframe?: boolean;
|
|
158
178
|
|
|
@@ -205,6 +225,21 @@ export type ParamsOfCreateOidc<
|
|
|
205
225
|
|
|
206
226
|
/** @deprecated: Use BASE_URL (same thing, just renamed). */
|
|
207
227
|
homeUrl?: string;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* This parameter is irrelevant in most usecases.
|
|
231
|
+
* It tells where to redirect after a successful login or autoLogin.
|
|
232
|
+
*
|
|
233
|
+
* If you are not in autoLogin mode there is absolutely no reason to use
|
|
234
|
+
* this parameter since you can pass `login({ redirectUrl: "..." })`.
|
|
235
|
+
*
|
|
236
|
+
* It can only be useful in some edge case with `autoLogin: true`
|
|
237
|
+
* When you want to precisely redirect somewhere after login.
|
|
238
|
+
*
|
|
239
|
+
* This can make sense if you have multiple clients to talk with different
|
|
240
|
+
* API and no iframe capabilities.
|
|
241
|
+
*/
|
|
242
|
+
postLoginRedirectUrl?: string;
|
|
208
243
|
};
|
|
209
244
|
|
|
210
245
|
const globalContext = {
|
|
@@ -338,8 +373,8 @@ export async function createOidc_nonMemoized<
|
|
|
338
373
|
__unsafe_clientSecret,
|
|
339
374
|
__unsafe_useIdTokenAsAccessToken = false,
|
|
340
375
|
__metadata,
|
|
341
|
-
|
|
342
|
-
|
|
376
|
+
scopes = ["openid", "profile"],
|
|
377
|
+
sessionRestorationMethod = params.autoLogin === true ? "full page redirect" : "auto"
|
|
343
378
|
} = params;
|
|
344
379
|
|
|
345
380
|
const BASE_URL_params = params.BASE_URL ?? params.homeUrl;
|
|
@@ -416,8 +451,15 @@ export async function createOidc_nonMemoized<
|
|
|
416
451
|
const oidcMetadata = __metadata ?? (await fetchOidcMetadata({ issuerUri }));
|
|
417
452
|
|
|
418
453
|
const canUseIframe = (() => {
|
|
419
|
-
|
|
420
|
-
|
|
454
|
+
switch (sessionRestorationMethod) {
|
|
455
|
+
case "auto":
|
|
456
|
+
break;
|
|
457
|
+
case "full page redirect":
|
|
458
|
+
return false;
|
|
459
|
+
case "iframe":
|
|
460
|
+
return true;
|
|
461
|
+
default:
|
|
462
|
+
assert<Equals<typeof sessionRestorationMethod, never>>;
|
|
421
463
|
}
|
|
422
464
|
|
|
423
465
|
third_party_cookies: {
|
|
@@ -525,7 +567,7 @@ export async function createOidc_nonMemoized<
|
|
|
525
567
|
];
|
|
526
568
|
})(),
|
|
527
569
|
"\n\nMore info:",
|
|
528
|
-
"https://docs.oidc-spa.dev/v/v8/resources/
|
|
570
|
+
"https://docs.oidc-spa.dev/v/v8/resources/third-party-cookies-and-session-restoration"
|
|
529
571
|
].join(" ")
|
|
530
572
|
);
|
|
531
573
|
} else {
|
|
@@ -555,7 +597,7 @@ export async function createOidc_nonMemoized<
|
|
|
555
597
|
];
|
|
556
598
|
})(),
|
|
557
599
|
"\nMore info:",
|
|
558
|
-
"https://docs.oidc-spa.dev/v/v8/resources/
|
|
600
|
+
"https://docs.oidc-spa.dev/v/v8/resources/third-party-cookies-and-session-restoration"
|
|
559
601
|
].join(" ")
|
|
560
602
|
);
|
|
561
603
|
}
|
|
@@ -599,7 +641,7 @@ export async function createOidc_nonMemoized<
|
|
|
599
641
|
return new InMemoryWebStorage();
|
|
600
642
|
}
|
|
601
643
|
|
|
602
|
-
const storage = createLazySessionStorage();
|
|
644
|
+
const storage = createLazySessionStorage({ storageId: configId });
|
|
603
645
|
|
|
604
646
|
if (evtIsThereMoreThanOneInstanceThatCantUserIframes.current) {
|
|
605
647
|
storage.persistCurrentStateAndSubsequentChanges();
|
|
@@ -896,7 +938,7 @@ export async function createOidc_nonMemoized<
|
|
|
896
938
|
redirectUri: homeUrlAndRedirectUri,
|
|
897
939
|
clientId,
|
|
898
940
|
issuerUri,
|
|
899
|
-
|
|
941
|
+
canUseIframe
|
|
900
942
|
});
|
|
901
943
|
}
|
|
902
944
|
|
|
@@ -957,11 +999,15 @@ export async function createOidc_nonMemoized<
|
|
|
957
999
|
action: "login",
|
|
958
1000
|
doForceReloadOnBfCache: true,
|
|
959
1001
|
redirectUrl: (() => {
|
|
960
|
-
if (
|
|
961
|
-
return
|
|
1002
|
+
if (postLoginRedirectUrl_default) {
|
|
1003
|
+
return postLoginRedirectUrl_default;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (!evtIsThereMoreThanOneInstanceThatCantUserIframes.current) {
|
|
1007
|
+
return getRootRelativeOriginalLocationHref();
|
|
962
1008
|
}
|
|
963
1009
|
|
|
964
|
-
return
|
|
1010
|
+
return getDesiredPostLoginRedirectUrl() ?? window.location.href;
|
|
965
1011
|
})(),
|
|
966
1012
|
// NOTE: Wether or not it's the preferred behavior, pushing to history
|
|
967
1013
|
// only works on user interaction so it have to be false
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
let postLoginRedirectUrl: string | undefined;
|
|
2
|
+
|
|
3
|
+
export function setDesiredPostLoginRedirectUrl(params: { postLoginRedirectUrl: string | undefined }) {
|
|
4
|
+
postLoginRedirectUrl = params.postLoginRedirectUrl;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getDesiredPostLoginRedirectUrl() {
|
|
8
|
+
return postLoginRedirectUrl;
|
|
9
|
+
}
|
package/src/core/diagnostic.ts
CHANGED
|
@@ -90,9 +90,9 @@ export async function createIframeTimeoutInitializationError(params: {
|
|
|
90
90
|
redirectUri: string;
|
|
91
91
|
issuerUri: string;
|
|
92
92
|
clientId: string;
|
|
93
|
-
|
|
93
|
+
canUseIframe: boolean;
|
|
94
94
|
}): Promise<OidcInitializationError> {
|
|
95
|
-
const { redirectUri, issuerUri, clientId,
|
|
95
|
+
const { redirectUri, issuerUri, clientId, canUseIframe } = params;
|
|
96
96
|
|
|
97
97
|
check_if_well_known_endpoint_is_reachable: {
|
|
98
98
|
const isValid = await getIsValidRemoteJson(`${issuerUri}${WELL_KNOWN_PATH}`);
|
|
@@ -105,7 +105,7 @@ export async function createIframeTimeoutInitializationError(params: {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
iframe_blocked: {
|
|
108
|
-
if (
|
|
108
|
+
if (!canUseIframe) {
|
|
109
109
|
break iframe_blocked;
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -189,7 +189,7 @@ export async function createIframeTimeoutInitializationError(params: {
|
|
|
189
189
|
messageOrCause: [
|
|
190
190
|
`${redirectUri} is currently served by your web server with the HTTP header \`${key_problem}: ${headers[key_problem]}\`.\n`,
|
|
191
191
|
"This header prevents the silent sign-in process from working.\n",
|
|
192
|
-
"Refer to this documentation page to fix this issue: https://docs.oidc-spa.dev/v/v8/resources/
|
|
192
|
+
"Refer to this documentation page to fix this issue: https://docs.oidc-spa.dev/v/v8/resources/third-party-cookies-and-session-restoration"
|
|
193
193
|
].join(" ")
|
|
194
194
|
});
|
|
195
195
|
}
|
|
@@ -23,7 +23,48 @@ import { type StatefulEvt, createStatefulEvt } from "../../tools/StatefulEvt";
|
|
|
23
23
|
import { readExpirationTimeInJwt } from "../../tools/readExpirationTimeInJwt";
|
|
24
24
|
|
|
25
25
|
type ConstructorParams = KeycloakServerConfig & {
|
|
26
|
+
/**
|
|
27
|
+
* NOTE: This parameter is optional if you use the Vite plugin.
|
|
28
|
+
*
|
|
29
|
+
* This parameter let's you overwrite the value provided in
|
|
30
|
+
* oidcEarlyInit({ BASE_URL: xxx });
|
|
31
|
+
*
|
|
32
|
+
* What should you put in this parameter?
|
|
33
|
+
* - Vite project: `BASE_URL: import.meta.env.BASE_URL`
|
|
34
|
+
* - Create React App project: `BASE_URL: process.env.PUBLIC_URL`
|
|
35
|
+
* - Other: `BASE_URL: "/"` (Usually, or `/dashboard` if your app is not at the root of the domain)
|
|
36
|
+
*/
|
|
26
37
|
BASE_URL?: string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Determines how session restoration is handled.
|
|
41
|
+
* Session restoration allows users to stay logged in between visits
|
|
42
|
+
* without needing to explicitly sign in each time.
|
|
43
|
+
*
|
|
44
|
+
* Options:
|
|
45
|
+
*
|
|
46
|
+
* - **"auto" (default)**:
|
|
47
|
+
* Automatically selects the best method.
|
|
48
|
+
* If the app’s domain shares a common parent domain with the authorization endpoint,
|
|
49
|
+
* an iframe is used for silent session restoration.
|
|
50
|
+
* Otherwise, a full-page redirect is used.
|
|
51
|
+
*
|
|
52
|
+
* - **"full page redirect"**:
|
|
53
|
+
* Forces full-page reloads for session restoration.
|
|
54
|
+
* Use this if your application is served with a restrictive CSP
|
|
55
|
+
* (e.g., `Content-Security-Policy: frame-ancestors "none"`)
|
|
56
|
+
* or `X-Frame-Options: DENY`, and you cannot modify those headers.
|
|
57
|
+
* This mode provides a slightly less seamless UX and will lead oidc-spa to
|
|
58
|
+
* store tokens in `localStorage` if multiple OIDC clients are used
|
|
59
|
+
* (e.g., your app communicates with several APIs).
|
|
60
|
+
*
|
|
61
|
+
* - **"iframe"**:
|
|
62
|
+
* Forces iframe-based session restoration.
|
|
63
|
+
* In development, if you go in your browser setting and allow your auth server’s domain
|
|
64
|
+
* to set third-party cookies this value will let you test your app
|
|
65
|
+
* with the local dev server as it will behave in production.
|
|
66
|
+
*/
|
|
67
|
+
sessionRestorationMethod?: "iframe" | "full page redirect" | "auto";
|
|
27
68
|
};
|
|
28
69
|
|
|
29
70
|
/**
|
|
@@ -99,7 +140,8 @@ export class Keycloak {
|
|
|
99
140
|
let hasCreateResolved = false;
|
|
100
141
|
|
|
101
142
|
const oidcOrError = await createOidc({
|
|
102
|
-
|
|
143
|
+
BASE_URL: constructorParams.BASE_URL,
|
|
144
|
+
sessionRestorationMethod: constructorParams.sessionRestorationMethod,
|
|
103
145
|
issuerUri,
|
|
104
146
|
clientId: this.#state.constructorParams.clientId,
|
|
105
147
|
autoLogin,
|
package/src/react/react.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import { id } from "../tools/tsafe/id";
|
|
|
15
15
|
import type { ValueOrAsyncGetter } from "../tools/ValueOrAsyncGetter";
|
|
16
16
|
import { Deferred } from "../tools/Deferred";
|
|
17
17
|
import { toFullyQualifiedUrl } from "../tools/toFullyQualifiedUrl";
|
|
18
|
+
import { setDesiredPostLoginRedirectUrl } from "../core/desiredPostLoginRedirectUrl";
|
|
18
19
|
|
|
19
20
|
export type OidcReact<DecodedIdToken extends Record<string, unknown>> =
|
|
20
21
|
| OidcReact.NotLoggedIn
|
|
@@ -409,20 +410,48 @@ export function createReactOidc_dependencyInjection<
|
|
|
409
410
|
|
|
410
411
|
const oidc = await getOidc();
|
|
411
412
|
|
|
413
|
+
const isUrlAlreadyReplaced =
|
|
414
|
+
window.location.href.replace(/\/$/, "") === redirectUrl.replace(/\/$/, "");
|
|
415
|
+
|
|
412
416
|
if (!oidc.isUserLoggedIn) {
|
|
413
417
|
if (cause === "preload") {
|
|
414
418
|
throw new Error(
|
|
415
419
|
"oidc-spa: User is not yet logged in. This is an expected error, nothing to be addressed."
|
|
416
420
|
);
|
|
417
421
|
}
|
|
418
|
-
const doesCurrentHrefRequiresAuth =
|
|
419
|
-
location.href.replace(/\/$/, "") === redirectUrl.replace(/\/$/, "");
|
|
420
422
|
|
|
421
423
|
await oidc.login({
|
|
422
424
|
redirectUrl,
|
|
423
|
-
doesCurrentHrefRequiresAuth
|
|
425
|
+
doesCurrentHrefRequiresAuth: isUrlAlreadyReplaced
|
|
424
426
|
});
|
|
425
427
|
}
|
|
428
|
+
|
|
429
|
+
define_temporary_postLoginRedirectUrl: {
|
|
430
|
+
if (isUrlAlreadyReplaced) {
|
|
431
|
+
break define_temporary_postLoginRedirectUrl;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
setDesiredPostLoginRedirectUrl({ postLoginRedirectUrl: redirectUrl });
|
|
435
|
+
|
|
436
|
+
const history_pushState = history.pushState;
|
|
437
|
+
const history_replaceState = history.replaceState;
|
|
438
|
+
|
|
439
|
+
const onNavigated = () => {
|
|
440
|
+
history.pushState = history_pushState;
|
|
441
|
+
history.replaceState = history_replaceState;
|
|
442
|
+
setDesiredPostLoginRedirectUrl({ postLoginRedirectUrl: undefined });
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
history.pushState = function pushState(...args) {
|
|
446
|
+
onNavigated();
|
|
447
|
+
return history_pushState.call(history, ...args);
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
history.replaceState = function replaceState(...args) {
|
|
451
|
+
onNavigated();
|
|
452
|
+
return history_replaceState.call(history, ...args);
|
|
453
|
+
};
|
|
454
|
+
}
|
|
426
455
|
}
|
|
427
456
|
|
|
428
457
|
async function getOidc(): Promise<Oidc<DecodedIdToken>> {
|
|
@@ -10,6 +10,7 @@ import { createObjectThatThrowsIfAccessed } from "../tools/createObjectThatThrow
|
|
|
10
10
|
import { createStatefulEvt } from "../tools/StatefulEvt";
|
|
11
11
|
import { id } from "../tools/tsafe/id";
|
|
12
12
|
import { toFullyQualifiedUrl } from "../tools/toFullyQualifiedUrl";
|
|
13
|
+
import { setDesiredPostLoginRedirectUrl } from "../core/desiredPostLoginRedirectUrl";
|
|
13
14
|
|
|
14
15
|
export function createOidcSpaApi<
|
|
15
16
|
AutoLogin extends boolean,
|
|
@@ -390,7 +391,7 @@ export function createOidcSpaApi<
|
|
|
390
391
|
transformUrlBeforeRedirect: paramsOfBootstrap.transformUrlBeforeRedirect,
|
|
391
392
|
extraQueryParams: paramsOfBootstrap.extraQueryParams,
|
|
392
393
|
extraTokenParams: paramsOfBootstrap.extraTokenParams,
|
|
393
|
-
|
|
394
|
+
sessionRestorationMethod: paramsOfBootstrap.sessionRestorationMethod,
|
|
394
395
|
debugLogs: paramsOfBootstrap.debugLogs,
|
|
395
396
|
__unsafe_clientSecret: paramsOfBootstrap.__unsafe_clientSecret,
|
|
396
397
|
__metadata: paramsOfBootstrap.__metadata,
|
|
@@ -440,25 +441,53 @@ export function createOidcSpaApi<
|
|
|
440
441
|
});
|
|
441
442
|
}
|
|
442
443
|
|
|
443
|
-
return location.href;
|
|
444
|
+
return window.location.href;
|
|
444
445
|
})();
|
|
445
446
|
|
|
446
447
|
const oidc = await getOidc();
|
|
447
448
|
|
|
449
|
+
const isUrlAlreadyReplaced =
|
|
450
|
+
window.location.href.replace(/\/$/, "") === redirectUrl.replace(/\/$/, "");
|
|
451
|
+
|
|
448
452
|
if (!oidc.isUserLoggedIn) {
|
|
449
453
|
if (cause === "preload") {
|
|
450
454
|
throw new Error(
|
|
451
455
|
"oidc-spa: User is not yet logged in. This is an expected error, nothing to be addressed."
|
|
452
456
|
);
|
|
453
457
|
}
|
|
454
|
-
const doesCurrentHrefRequiresAuth =
|
|
455
|
-
location.href.replace(/\/$/, "") === redirectUrl.replace(/\/$/, "");
|
|
456
458
|
|
|
457
459
|
await oidc.login({
|
|
458
460
|
redirectUrl,
|
|
459
|
-
doesCurrentHrefRequiresAuth
|
|
461
|
+
doesCurrentHrefRequiresAuth: isUrlAlreadyReplaced
|
|
460
462
|
});
|
|
461
463
|
}
|
|
464
|
+
|
|
465
|
+
define_temporary_postLoginRedirectUrl: {
|
|
466
|
+
if (isUrlAlreadyReplaced) {
|
|
467
|
+
break define_temporary_postLoginRedirectUrl;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
setDesiredPostLoginRedirectUrl({ postLoginRedirectUrl: redirectUrl });
|
|
471
|
+
|
|
472
|
+
const history_pushState = history.pushState;
|
|
473
|
+
const history_replaceState = history.replaceState;
|
|
474
|
+
|
|
475
|
+
const onNavigated = () => {
|
|
476
|
+
history.pushState = history_pushState;
|
|
477
|
+
history.replaceState = history_replaceState;
|
|
478
|
+
setDesiredPostLoginRedirectUrl({ postLoginRedirectUrl: undefined });
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
history.pushState = function pushState(...args) {
|
|
482
|
+
onNavigated();
|
|
483
|
+
return history_pushState.call(history, ...args);
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
history.replaceState = function replaceState(...args) {
|
|
487
|
+
onNavigated();
|
|
488
|
+
return history_replaceState.call(history, ...args);
|
|
489
|
+
};
|
|
490
|
+
}
|
|
462
491
|
}
|
|
463
492
|
|
|
464
493
|
function OidcInitializationErrorGate(props: {
|
package/src/react-spa/types.tsx
CHANGED
|
@@ -213,11 +213,34 @@ export namespace ParamsOfBootstrap {
|
|
|
213
213
|
| (() => Record<string, string | undefined>);
|
|
214
214
|
|
|
215
215
|
/**
|
|
216
|
-
*
|
|
216
|
+
* Determines how session restoration is handled.
|
|
217
|
+
* Session restoration allows users to stay logged in between visits
|
|
218
|
+
* without needing to explicitly sign in each time.
|
|
217
219
|
*
|
|
218
|
-
*
|
|
220
|
+
* Options:
|
|
221
|
+
*
|
|
222
|
+
* - **"auto" (default)**:
|
|
223
|
+
* Automatically selects the best method.
|
|
224
|
+
* If the app’s domain shares a common parent domain with the authorization endpoint,
|
|
225
|
+
* an iframe is used for silent session restoration.
|
|
226
|
+
* Otherwise, a full-page redirect is used.
|
|
227
|
+
*
|
|
228
|
+
* - **"full page redirect"**:
|
|
229
|
+
* Forces full-page reloads for session restoration.
|
|
230
|
+
* Use this if your application is served with a restrictive CSP
|
|
231
|
+
* (e.g., `Content-Security-Policy: frame-ancestors "none"`)
|
|
232
|
+
* or `X-Frame-Options: DENY`, and you cannot modify those headers.
|
|
233
|
+
* This mode provides a slightly less seamless UX and will lead oidc-spa to
|
|
234
|
+
* store tokens in `localStorage` if multiple OIDC clients are used
|
|
235
|
+
* (e.g., your app communicates with several APIs).
|
|
236
|
+
*
|
|
237
|
+
* - **"iframe"**:
|
|
238
|
+
* Forces iframe-based session restoration.
|
|
239
|
+
* In development, if you go in your browser setting and allow your auth server’s domain
|
|
240
|
+
* to set third-party cookies this value will let you test your app
|
|
241
|
+
* with the local dev server as it will behave in production.
|
|
219
242
|
*/
|
|
220
|
-
|
|
243
|
+
sessionRestorationMethod?: "iframe" | "full page redirect" | "auto";
|
|
221
244
|
|
|
222
245
|
debugLogs?: boolean;
|
|
223
246
|
|
|
@@ -30,6 +30,7 @@ import { createServerFn, createMiddleware } from "@tanstack/react-start";
|
|
|
30
30
|
import { getRequest, setResponseHeader, setResponseStatus } from "@tanstack/react-start/server";
|
|
31
31
|
import { toFullyQualifiedUrl } from "../../tools/toFullyQualifiedUrl";
|
|
32
32
|
import { UnifiedClientRetryForSsrLoadersError } from "./rfcUnifiedClientRetryForSsrLoaders/UnifiedClientRetryForSsrLoadersError";
|
|
33
|
+
import { setDesiredPostLoginRedirectUrl } from "../../core/desiredPostLoginRedirectUrl";
|
|
33
34
|
|
|
34
35
|
export function createOidcSpaApi<
|
|
35
36
|
AutoLogin extends boolean,
|
|
@@ -662,7 +663,7 @@ export function createOidcSpaApi<
|
|
|
662
663
|
transformUrlBeforeRedirect: paramsOfBootstrap.transformUrlBeforeRedirect,
|
|
663
664
|
extraQueryParams: paramsOfBootstrap.extraQueryParams,
|
|
664
665
|
extraTokenParams: paramsOfBootstrap.extraTokenParams,
|
|
665
|
-
|
|
666
|
+
sessionRestorationMethod: paramsOfBootstrap.sessionRestorationMethod,
|
|
666
667
|
debugLogs: paramsOfBootstrap.debugLogs,
|
|
667
668
|
__unsafe_clientSecret: paramsOfBootstrap.__unsafe_clientSecret,
|
|
668
669
|
__metadata: paramsOfBootstrap.__metadata,
|
|
@@ -714,6 +715,9 @@ export function createOidcSpaApi<
|
|
|
714
715
|
|
|
715
716
|
const oidc = await getOidc();
|
|
716
717
|
|
|
718
|
+
const isUrlAlreadyReplaced =
|
|
719
|
+
window.location.href.replace(/\/$/, "") === redirectUrl.replace(/\/$/, "");
|
|
720
|
+
|
|
717
721
|
if (!oidc.isUserLoggedIn) {
|
|
718
722
|
if (cause === "preload") {
|
|
719
723
|
throw new Error(
|
|
@@ -724,14 +728,39 @@ export function createOidcSpaApi<
|
|
|
724
728
|
].join(" ")
|
|
725
729
|
);
|
|
726
730
|
}
|
|
727
|
-
const doesCurrentHrefRequiresAuth =
|
|
728
|
-
location.href.replace(/\/$/, "") === redirectUrl.replace(/\/$/, "");
|
|
729
731
|
|
|
730
732
|
await oidc.login({
|
|
731
733
|
redirectUrl,
|
|
732
|
-
doesCurrentHrefRequiresAuth
|
|
734
|
+
doesCurrentHrefRequiresAuth: isUrlAlreadyReplaced
|
|
733
735
|
});
|
|
734
736
|
}
|
|
737
|
+
|
|
738
|
+
define_temporary_postLoginRedirectUrl: {
|
|
739
|
+
if (isUrlAlreadyReplaced) {
|
|
740
|
+
break define_temporary_postLoginRedirectUrl;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
setDesiredPostLoginRedirectUrl({ postLoginRedirectUrl: redirectUrl });
|
|
744
|
+
|
|
745
|
+
const history_pushState = history.pushState;
|
|
746
|
+
const history_replaceState = history.replaceState;
|
|
747
|
+
|
|
748
|
+
const onNavigated = () => {
|
|
749
|
+
history.pushState = history_pushState;
|
|
750
|
+
history.replaceState = history_replaceState;
|
|
751
|
+
setDesiredPostLoginRedirectUrl({ postLoginRedirectUrl: undefined });
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
history.pushState = function pushState(...args) {
|
|
755
|
+
onNavigated();
|
|
756
|
+
return history_pushState.call(history, ...args);
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
history.replaceState = function replaceState(...args) {
|
|
760
|
+
onNavigated();
|
|
761
|
+
return history_replaceState.call(history, ...args);
|
|
762
|
+
};
|
|
763
|
+
}
|
|
735
764
|
}
|
|
736
765
|
|
|
737
766
|
enforceLogin.__isOidcSpaEnforceLogin = true;
|
|
@@ -306,11 +306,34 @@ export namespace ParamsOfBootstrap {
|
|
|
306
306
|
| (() => Record<string, string | undefined>);
|
|
307
307
|
|
|
308
308
|
/**
|
|
309
|
-
*
|
|
309
|
+
* Determines how session restoration is handled.
|
|
310
|
+
* Session restoration allows users to stay logged in between visits
|
|
311
|
+
* without needing to explicitly sign in each time.
|
|
310
312
|
*
|
|
311
|
-
*
|
|
313
|
+
* Options:
|
|
314
|
+
*
|
|
315
|
+
* - **"auto" (default)**:
|
|
316
|
+
* Automatically selects the best method.
|
|
317
|
+
* If the app’s domain shares a common parent domain with the authorization endpoint,
|
|
318
|
+
* an iframe is used for silent session restoration.
|
|
319
|
+
* Otherwise, a full-page redirect is used.
|
|
320
|
+
*
|
|
321
|
+
* - **"full page redirect"**:
|
|
322
|
+
* Forces full-page reloads for session restoration.
|
|
323
|
+
* Use this if your application is served with a restrictive CSP
|
|
324
|
+
* (e.g., `Content-Security-Policy: frame-ancestors "none"`)
|
|
325
|
+
* or `X-Frame-Options: DENY`, and you cannot modify those headers.
|
|
326
|
+
* This mode provides a slightly less seamless UX and will lead oidc-spa to
|
|
327
|
+
* store tokens in `localStorage` if multiple OIDC clients are used
|
|
328
|
+
* (e.g., your app communicates with several APIs).
|
|
329
|
+
*
|
|
330
|
+
* - **"iframe"**:
|
|
331
|
+
* Forces iframe-based session restoration.
|
|
332
|
+
* In development, if you go in your browser setting and allow your auth server’s domain
|
|
333
|
+
* to set third-party cookies this value will let you test your app
|
|
334
|
+
* with the local dev server as it will behave in production.
|
|
312
335
|
*/
|
|
313
|
-
|
|
336
|
+
sessionRestorationMethod?: "iframe" | "full page redirect" | "auto";
|
|
314
337
|
|
|
315
338
|
debugLogs?: boolean;
|
|
316
339
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { assert } from "../tools/tsafe/assert";
|
|
2
2
|
|
|
3
|
-
const SESSION_STORAGE_PREFIX = "lazy-session-storage:";
|
|
4
|
-
|
|
5
3
|
export type LazySessionStorage = {
|
|
6
4
|
// `Storage` methods, we don't use the type directly because it has [name: string]: any;
|
|
7
5
|
readonly length: number;
|
|
@@ -15,14 +13,20 @@ export type LazySessionStorage = {
|
|
|
15
13
|
persistCurrentStateAndSubsequentChanges: () => void;
|
|
16
14
|
};
|
|
17
15
|
|
|
18
|
-
export function createLazySessionStorage(): LazySessionStorage {
|
|
16
|
+
export function createLazySessionStorage(params: { storageId: string }): LazySessionStorage {
|
|
17
|
+
const { storageId } = params;
|
|
18
|
+
|
|
19
|
+
const sessionStoragePrefix = `lazy-session-storage:${storageId}:`;
|
|
20
|
+
|
|
21
|
+
const getSessionStorageKey = (key: string) => `${sessionStoragePrefix}${key}`;
|
|
22
|
+
|
|
19
23
|
const entries: { key: string; value: string }[] = [];
|
|
20
24
|
|
|
21
25
|
for (let i = 0; i < sessionStorage.length; i++) {
|
|
22
26
|
const key = sessionStorage.key(i);
|
|
23
27
|
assert(key !== null, "470498");
|
|
24
28
|
|
|
25
|
-
if (!key.startsWith(
|
|
29
|
+
if (!key.startsWith(sessionStoragePrefix)) {
|
|
26
30
|
continue;
|
|
27
31
|
}
|
|
28
32
|
|
|
@@ -33,7 +37,7 @@ export function createLazySessionStorage(): LazySessionStorage {
|
|
|
33
37
|
sessionStorage.removeItem(key);
|
|
34
38
|
|
|
35
39
|
entries.push({
|
|
36
|
-
key: key.slice(
|
|
40
|
+
key: key.slice(sessionStoragePrefix.length),
|
|
37
41
|
value
|
|
38
42
|
});
|
|
39
43
|
}
|
|
@@ -74,7 +78,7 @@ export function createLazySessionStorage(): LazySessionStorage {
|
|
|
74
78
|
return;
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
sessionStorage.removeItem(
|
|
81
|
+
sessionStorage.removeItem(getSessionStorageKey(entry.key));
|
|
78
82
|
|
|
79
83
|
const index = entries.indexOf(entry);
|
|
80
84
|
|
|
@@ -96,7 +100,7 @@ export function createLazySessionStorage(): LazySessionStorage {
|
|
|
96
100
|
},
|
|
97
101
|
setItem: (key, value) => {
|
|
98
102
|
if (isPersistenceEnabled) {
|
|
99
|
-
sessionStorage.setItem(
|
|
103
|
+
sessionStorage.setItem(getSessionStorageKey(key), value);
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
update: {
|
|
@@ -7,4 +7,6 @@ export type LazySessionStorage = {
|
|
|
7
7
|
setItem(key: string, value: string): void;
|
|
8
8
|
persistCurrentStateAndSubsequentChanges: () => void;
|
|
9
9
|
};
|
|
10
|
-
export declare function createLazySessionStorage(
|
|
10
|
+
export declare function createLazySessionStorage(params: {
|
|
11
|
+
storageId: string;
|
|
12
|
+
}): LazySessionStorage;
|