oidc-spa 8.2.2 → 8.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/Oidc.d.ts +1 -0
- package/core/createOidc.d.ts +48 -16
- package/core/createOidc.js +27 -36
- 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/core/homeAndRedirectUri.d.ts +5 -0
- package/core/homeAndRedirectUri.js +32 -0
- package/core/homeAndRedirectUri.js.map +1 -0
- package/esm/angular.d.ts +28 -4
- package/esm/angular.js +31 -6
- package/esm/angular.js.map +1 -1
- package/esm/core/Oidc.d.ts +1 -0
- package/esm/core/createOidc.d.ts +48 -16
- package/esm/core/createOidc.js +27 -36
- 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/core/homeAndRedirectUri.d.ts +5 -0
- package/esm/core/homeAndRedirectUri.js +29 -0
- package/esm/core/homeAndRedirectUri.js.map +1 -0
- package/esm/keycloak/keycloak-js/Keycloak.d.ts +40 -0
- package/esm/keycloak/keycloak-js/Keycloak.js +13 -3
- package/esm/keycloak/keycloak-js/Keycloak.js.map +1 -1
- package/esm/keycloak/keycloakUtils.d.ts +3 -1
- package/esm/keycloak/keycloakUtils.js +26 -5
- package/esm/keycloak/keycloakUtils.js.map +1 -1
- package/esm/mock/oidc.js +2 -1
- package/esm/mock/oidc.js.map +1 -1
- package/esm/react/react.js +24 -2
- package/esm/react/react.js.map +1 -1
- package/esm/react-spa/apiBuilder.d.ts +2 -2
- package/esm/react-spa/apiBuilder.js +1 -1
- package/esm/react-spa/apiBuilder.js.map +1 -1
- package/esm/react-spa/createOidcSpaApi.js +29 -5
- package/esm/react-spa/createOidcSpaApi.js.map +1 -1
- package/esm/react-spa/types.d.ts +27 -3
- package/esm/tanstack-start/react/apiBuilder.d.ts +2 -2
- package/esm/tanstack-start/react/apiBuilder.js +1 -1
- package/esm/tanstack-start/react/apiBuilder.js.map +1 -1
- package/esm/tanstack-start/react/createOidcSpaApi.js +28 -4
- package/esm/tanstack-start/react/createOidcSpaApi.js.map +1 -1
- package/esm/tanstack-start/react/types.d.ts +27 -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/esm/tools/parseKeycloakIssuerUri.js +5 -1
- package/esm/tools/parseKeycloakIssuerUri.js.map +1 -1
- package/keycloak/keycloak-js/Keycloak.d.ts +40 -0
- package/keycloak/keycloak-js/Keycloak.js +13 -3
- package/keycloak/keycloak-js/Keycloak.js.map +1 -1
- package/keycloak/keycloakUtils.d.ts +3 -1
- package/keycloak/keycloakUtils.js +26 -5
- package/keycloak/keycloakUtils.js.map +1 -1
- package/mock/oidc.js +2 -1
- package/mock/oidc.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/apiBuilder.d.ts +2 -2
- package/react-spa/apiBuilder.js +1 -1
- package/react-spa/apiBuilder.js.map +1 -1
- package/react-spa/createOidcSpaApi.js +29 -5
- package/react-spa/createOidcSpaApi.js.map +1 -1
- package/react-spa/types.d.ts +27 -3
- package/src/angular.ts +76 -18
- package/src/core/Oidc.ts +1 -0
- package/src/core/createOidc.ts +78 -57
- package/src/core/desiredPostLoginRedirectUrl.ts +9 -0
- package/src/core/diagnostic.ts +4 -4
- package/src/core/homeAndRedirectUri.ts +36 -0
- package/src/keycloak/keycloak-js/Keycloak.ts +56 -3
- package/src/keycloak/keycloakUtils.ts +38 -6
- package/src/mock/oidc.ts +2 -1
- package/src/react/react.tsx +32 -3
- package/src/react-spa/apiBuilder.ts +3 -3
- package/src/react-spa/createOidcSpaApi.tsx +37 -6
- package/src/react-spa/types.tsx +27 -3
- package/src/tanstack-start/react/apiBuilder.ts +3 -3
- package/src/tanstack-start/react/createOidcSpaApi.tsx +36 -5
- package/src/tanstack-start/react/types.tsx +27 -3
- package/src/tools/lazySessionStorage.ts +11 -7
- package/src/tools/parseKeycloakIssuerUri.ts +6 -1
- 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/tools/parseKeycloakIssuerUri.js +5 -1
- package/tools/parseKeycloakIssuerUri.js.map +1 -1
- package/vite-plugin/manageOptimizedDeps.js +2 -1
- package/vite-plugin/manageOptimizedDeps.js.map +1 -1
package/src/angular.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { Router, type CanActivateFn } from "@angular/router";
|
|
|
19
19
|
import type { ValueOrAsyncGetter } from "./tools/ValueOrAsyncGetter";
|
|
20
20
|
import { getBaseHref } from "./tools/getBaseHref";
|
|
21
21
|
import type { ConcreteClass } from "./tools/ConcreteClass";
|
|
22
|
+
import { setDesiredPostLoginRedirectUrl } from "./core/desiredPostLoginRedirectUrl";
|
|
22
23
|
|
|
23
24
|
export type ParamsOfProvide = {
|
|
24
25
|
issuerUri: string;
|
|
@@ -96,11 +97,34 @@ export type ParamsOfProvide = {
|
|
|
96
97
|
autoLogin?: boolean;
|
|
97
98
|
|
|
98
99
|
/**
|
|
99
|
-
*
|
|
100
|
+
* Determines how session restoration is handled.
|
|
101
|
+
* Session restoration allows users to stay logged in between visits
|
|
102
|
+
* without needing to explicitly sign in each time.
|
|
100
103
|
*
|
|
101
|
-
*
|
|
104
|
+
* Options:
|
|
105
|
+
*
|
|
106
|
+
* - **"auto" (default)**:
|
|
107
|
+
* Automatically selects the best method.
|
|
108
|
+
* If the app’s domain shares a common parent domain with the authorization endpoint,
|
|
109
|
+
* an iframe is used for silent session restoration.
|
|
110
|
+
* Otherwise, a full-page redirect is used.
|
|
111
|
+
*
|
|
112
|
+
* - **"full page redirect"**:
|
|
113
|
+
* Forces full-page reloads for session restoration.
|
|
114
|
+
* Use this if your application is served with a restrictive CSP
|
|
115
|
+
* (e.g., `Content-Security-Policy: frame-ancestors "none"`)
|
|
116
|
+
* or `X-Frame-Options: DENY`, and you cannot modify those headers.
|
|
117
|
+
* This mode provides a slightly less seamless UX and will lead oidc-spa to
|
|
118
|
+
* store tokens in `localStorage` if multiple OIDC clients are used
|
|
119
|
+
* (e.g., your app communicates with several APIs).
|
|
120
|
+
*
|
|
121
|
+
* - **"iframe"**:
|
|
122
|
+
* Forces iframe-based session restoration.
|
|
123
|
+
* In development, if you go in your browser setting and allow your auth server’s domain
|
|
124
|
+
* to set third-party cookies this value will let you test your app
|
|
125
|
+
* with the local dev server as it will behave in production.
|
|
102
126
|
*/
|
|
103
|
-
|
|
127
|
+
sessionRestorationMethod?: "iframe" | "full page redirect" | "auto";
|
|
104
128
|
|
|
105
129
|
debugLogs?: boolean;
|
|
106
130
|
|
|
@@ -150,7 +174,10 @@ export type ParamsOfProvide = {
|
|
|
150
174
|
assert<
|
|
151
175
|
Equals<
|
|
152
176
|
Omit<ParamsOfProvide, "autoLogoutWarningDurationSeconds">,
|
|
153
|
-
Omit<
|
|
177
|
+
Omit<
|
|
178
|
+
ParamsOfCreateOidc<any, boolean>,
|
|
179
|
+
"homeUrl" | "BASE_URL" | "noIframe" | "decodedIdTokenSchema"
|
|
180
|
+
>
|
|
154
181
|
>
|
|
155
182
|
>;
|
|
156
183
|
|
|
@@ -454,28 +481,55 @@ export abstract class AbstractOidcService<
|
|
|
454
481
|
|
|
455
482
|
await instance.prInitialized;
|
|
456
483
|
|
|
457
|
-
const
|
|
484
|
+
const redirectUrl = router.serializeUrl(
|
|
485
|
+
router.createUrlTree(
|
|
486
|
+
route.url.map(u => u.path),
|
|
487
|
+
{
|
|
488
|
+
queryParams: route.queryParams,
|
|
489
|
+
fragment: route.fragment ?? undefined
|
|
490
|
+
}
|
|
491
|
+
)
|
|
492
|
+
);
|
|
458
493
|
|
|
459
|
-
|
|
460
|
-
const redirectUrl = router.serializeUrl(
|
|
461
|
-
router.createUrlTree(
|
|
462
|
-
route.url.map(u => u.path),
|
|
463
|
-
{
|
|
464
|
-
queryParams: route.queryParams,
|
|
465
|
-
fragment: route.fragment ?? undefined
|
|
466
|
-
}
|
|
467
|
-
)
|
|
468
|
-
);
|
|
494
|
+
const oidc = instance.#getOidc({ callerName: "enforceLoginGuard" });
|
|
469
495
|
|
|
470
|
-
|
|
471
|
-
|
|
496
|
+
const isUrlAlreadyReplaced =
|
|
497
|
+
window.location.href.replace(/\/$/, "") === redirectUrl.replace(/\/$/, "");
|
|
472
498
|
|
|
499
|
+
if (!oidc.isUserLoggedIn) {
|
|
473
500
|
await oidc.login({
|
|
474
|
-
doesCurrentHrefRequiresAuth,
|
|
501
|
+
doesCurrentHrefRequiresAuth: isUrlAlreadyReplaced,
|
|
475
502
|
redirectUrl
|
|
476
503
|
});
|
|
477
504
|
}
|
|
478
505
|
|
|
506
|
+
define_temporary_postLoginRedirectUrl: {
|
|
507
|
+
if (isUrlAlreadyReplaced) {
|
|
508
|
+
break define_temporary_postLoginRedirectUrl;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
setDesiredPostLoginRedirectUrl({ postLoginRedirectUrl: redirectUrl });
|
|
512
|
+
|
|
513
|
+
const history_pushState = history.pushState;
|
|
514
|
+
const history_replaceState = history.replaceState;
|
|
515
|
+
|
|
516
|
+
const onNavigated = () => {
|
|
517
|
+
history.pushState = history_pushState;
|
|
518
|
+
history.replaceState = history_replaceState;
|
|
519
|
+
setDesiredPostLoginRedirectUrl({ postLoginRedirectUrl: undefined });
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
history.pushState = function pushState(...args) {
|
|
523
|
+
onNavigated();
|
|
524
|
+
return history_pushState.call(history, ...args);
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
history.replaceState = function replaceState(...args) {
|
|
528
|
+
onNavigated();
|
|
529
|
+
return history_replaceState.call(history, ...args);
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
479
533
|
return true;
|
|
480
534
|
}) satisfies CanActivateFn;
|
|
481
535
|
return canActivateFn;
|
|
@@ -569,6 +623,10 @@ export abstract class AbstractOidcService<
|
|
|
569
623
|
return this.#getOidc({ callerName: "clientId" }).params.clientId;
|
|
570
624
|
}
|
|
571
625
|
|
|
626
|
+
get validRedirectUri() {
|
|
627
|
+
return this.#getOidc({ callerName: "validRedirectUri" }).params.validRedirectUri;
|
|
628
|
+
}
|
|
629
|
+
|
|
572
630
|
#isUserLoggedIn_override: boolean | undefined = undefined;
|
|
573
631
|
|
|
574
632
|
get isUserLoggedIn() {
|
package/src/core/Oidc.ts
CHANGED
package/src/core/createOidc.ts
CHANGED
|
@@ -45,13 +45,14 @@ import { getIsOnline } from "../tools/getIsOnline";
|
|
|
45
45
|
import { isKeycloak } from "../keycloak/isKeycloak";
|
|
46
46
|
import { INFINITY_TIME } from "../tools/INFINITY_TIME";
|
|
47
47
|
import { prShouldLoadApp } from "./prShouldLoadApp";
|
|
48
|
-
import { getBASE_URL } from "./BASE_URL";
|
|
49
48
|
import { getIsLikelyDevServer } from "../tools/isLikelyDevServer";
|
|
50
49
|
import { createObjectThatThrowsIfAccessed } from "../tools/createObjectThatThrowsIfAccessed";
|
|
51
50
|
import {
|
|
52
51
|
evtIsThereMoreThanOneInstanceThatCantUserIframes,
|
|
53
52
|
notifyNewInstanceThatCantUseIframes
|
|
54
53
|
} from "./instancesThatCantUseIframes";
|
|
54
|
+
import { getDesiredPostLoginRedirectUrl } from "./desiredPostLoginRedirectUrl";
|
|
55
|
+
import { getHomeAndRedirectUri } from "./homeAndRedirectUri";
|
|
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;
|
|
@@ -370,33 +405,7 @@ export async function createOidc_nonMemoized<
|
|
|
370
405
|
return extraTokenParamsOrGetter;
|
|
371
406
|
})();
|
|
372
407
|
|
|
373
|
-
const homeUrlAndRedirectUri =
|
|
374
|
-
urlish: (() => {
|
|
375
|
-
if (BASE_URL_params !== undefined) {
|
|
376
|
-
return BASE_URL_params;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const BASE_URL = getBASE_URL();
|
|
380
|
-
|
|
381
|
-
if (BASE_URL === undefined) {
|
|
382
|
-
throw new Error(
|
|
383
|
-
[
|
|
384
|
-
"oidc-spa: If you do not use the oidc-spa Vite plugin",
|
|
385
|
-
"you must provide the BASE_URL to the earlyInit() examples:",
|
|
386
|
-
"oidcSpaEarlyInit({ BASE_URL: import.meta.env.BASE_URL })",
|
|
387
|
-
"oidcSpaEarlyInit({ BASE_URL: '/' })",
|
|
388
|
-
"",
|
|
389
|
-
"You can also pass this parameter to createOidc({ BASE_URL: '...' })",
|
|
390
|
-
"or bootstrapOidc({ BASE_URL: '...' })"
|
|
391
|
-
].join("\n")
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return BASE_URL;
|
|
396
|
-
})(),
|
|
397
|
-
doAssertNoQueryParams: true,
|
|
398
|
-
doOutputWithTrailingSlash: true
|
|
399
|
-
});
|
|
408
|
+
const { homeUrlAndRedirectUri } = getHomeAndRedirectUri({ BASE_URL_params });
|
|
400
409
|
|
|
401
410
|
log?.(
|
|
402
411
|
`Calling createOidc v${VERSION} ${JSON.stringify(
|
|
@@ -404,7 +413,7 @@ export async function createOidc_nonMemoized<
|
|
|
404
413
|
issuerUri,
|
|
405
414
|
clientId,
|
|
406
415
|
scopes,
|
|
407
|
-
|
|
416
|
+
validRedirectUri: homeUrlAndRedirectUri
|
|
408
417
|
},
|
|
409
418
|
null,
|
|
410
419
|
2
|
|
@@ -416,8 +425,15 @@ export async function createOidc_nonMemoized<
|
|
|
416
425
|
const oidcMetadata = __metadata ?? (await fetchOidcMetadata({ issuerUri }));
|
|
417
426
|
|
|
418
427
|
const canUseIframe = (() => {
|
|
419
|
-
|
|
420
|
-
|
|
428
|
+
switch (sessionRestorationMethod) {
|
|
429
|
+
case "auto":
|
|
430
|
+
break;
|
|
431
|
+
case "full page redirect":
|
|
432
|
+
return false;
|
|
433
|
+
case "iframe":
|
|
434
|
+
return true;
|
|
435
|
+
default:
|
|
436
|
+
assert<Equals<typeof sessionRestorationMethod, never>>;
|
|
421
437
|
}
|
|
422
438
|
|
|
423
439
|
third_party_cookies: {
|
|
@@ -525,7 +541,7 @@ export async function createOidc_nonMemoized<
|
|
|
525
541
|
];
|
|
526
542
|
})(),
|
|
527
543
|
"\n\nMore info:",
|
|
528
|
-
"https://docs.oidc-spa.dev/v/v8/resources/
|
|
544
|
+
"https://docs.oidc-spa.dev/v/v8/resources/third-party-cookies-and-session-restoration"
|
|
529
545
|
].join(" ")
|
|
530
546
|
);
|
|
531
547
|
} else {
|
|
@@ -555,7 +571,7 @@ export async function createOidc_nonMemoized<
|
|
|
555
571
|
];
|
|
556
572
|
})(),
|
|
557
573
|
"\nMore info:",
|
|
558
|
-
"https://docs.oidc-spa.dev/v/v8/resources/
|
|
574
|
+
"https://docs.oidc-spa.dev/v/v8/resources/third-party-cookies-and-session-restoration"
|
|
559
575
|
].join(" ")
|
|
560
576
|
);
|
|
561
577
|
}
|
|
@@ -599,7 +615,7 @@ export async function createOidc_nonMemoized<
|
|
|
599
615
|
return new InMemoryWebStorage();
|
|
600
616
|
}
|
|
601
617
|
|
|
602
|
-
const storage = createLazySessionStorage();
|
|
618
|
+
const storage = createLazySessionStorage({ storageId: configId });
|
|
603
619
|
|
|
604
620
|
if (evtIsThereMoreThanOneInstanceThatCantUserIframes.current) {
|
|
605
621
|
storage.persistCurrentStateAndSubsequentChanges();
|
|
@@ -896,7 +912,7 @@ export async function createOidc_nonMemoized<
|
|
|
896
912
|
redirectUri: homeUrlAndRedirectUri,
|
|
897
913
|
clientId,
|
|
898
914
|
issuerUri,
|
|
899
|
-
|
|
915
|
+
canUseIframe
|
|
900
916
|
});
|
|
901
917
|
}
|
|
902
918
|
|
|
@@ -957,11 +973,15 @@ export async function createOidc_nonMemoized<
|
|
|
957
973
|
action: "login",
|
|
958
974
|
doForceReloadOnBfCache: true,
|
|
959
975
|
redirectUrl: (() => {
|
|
960
|
-
if (
|
|
961
|
-
return
|
|
976
|
+
if (postLoginRedirectUrl_default) {
|
|
977
|
+
return postLoginRedirectUrl_default;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (!evtIsThereMoreThanOneInstanceThatCantUserIframes.current) {
|
|
981
|
+
return getRootRelativeOriginalLocationHref();
|
|
962
982
|
}
|
|
963
983
|
|
|
964
|
-
return
|
|
984
|
+
return getDesiredPostLoginRedirectUrl() ?? window.location.href;
|
|
965
985
|
})(),
|
|
966
986
|
// NOTE: Wether or not it's the preferred behavior, pushing to history
|
|
967
987
|
// only works on user interaction so it have to be false
|
|
@@ -1020,7 +1040,8 @@ export async function createOidc_nonMemoized<
|
|
|
1020
1040
|
const oidc_common: Oidc.Common = {
|
|
1021
1041
|
params: {
|
|
1022
1042
|
issuerUri,
|
|
1023
|
-
clientId
|
|
1043
|
+
clientId,
|
|
1044
|
+
validRedirectUri: homeUrlAndRedirectUri
|
|
1024
1045
|
}
|
|
1025
1046
|
};
|
|
1026
1047
|
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { toFullyQualifiedUrl } from "../tools/toFullyQualifiedUrl";
|
|
2
|
+
import { getBASE_URL } from "./BASE_URL";
|
|
3
|
+
|
|
4
|
+
export function getHomeAndRedirectUri(params: { BASE_URL_params: string | undefined }) {
|
|
5
|
+
const { BASE_URL_params } = params;
|
|
6
|
+
|
|
7
|
+
const homeUrlAndRedirectUri = toFullyQualifiedUrl({
|
|
8
|
+
urlish: (() => {
|
|
9
|
+
if (BASE_URL_params !== undefined) {
|
|
10
|
+
return BASE_URL_params;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const BASE_URL = getBASE_URL();
|
|
14
|
+
|
|
15
|
+
if (BASE_URL === undefined) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
[
|
|
18
|
+
"oidc-spa: If you do not use the oidc-spa Vite plugin",
|
|
19
|
+
"you must provide the BASE_URL to the earlyInit() examples:",
|
|
20
|
+
"oidcSpaEarlyInit({ BASE_URL: import.meta.env.BASE_URL })",
|
|
21
|
+
"oidcSpaEarlyInit({ BASE_URL: '/' })",
|
|
22
|
+
"",
|
|
23
|
+
"You can also pass this parameter to createOidc({ BASE_URL: '...' })",
|
|
24
|
+
"or bootstrapOidc({ BASE_URL: '...' })"
|
|
25
|
+
].join("\n")
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return BASE_URL;
|
|
30
|
+
})(),
|
|
31
|
+
doAssertNoQueryParams: true,
|
|
32
|
+
doOutputWithTrailingSlash: true
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return { homeUrlAndRedirectUri };
|
|
36
|
+
}
|
|
@@ -21,9 +21,51 @@ import { type KeycloakUtils, createKeycloakUtils } from "../keycloakUtils";
|
|
|
21
21
|
import { workerTimers } from "../../vendor/frontend/worker-timers";
|
|
22
22
|
import { type StatefulEvt, createStatefulEvt } from "../../tools/StatefulEvt";
|
|
23
23
|
import { readExpirationTimeInJwt } from "../../tools/readExpirationTimeInJwt";
|
|
24
|
+
import { getHomeAndRedirectUri } from "../../core/homeAndRedirectUri";
|
|
24
25
|
|
|
25
26
|
type ConstructorParams = KeycloakServerConfig & {
|
|
27
|
+
/**
|
|
28
|
+
* NOTE: This parameter is optional if you use the Vite plugin.
|
|
29
|
+
*
|
|
30
|
+
* This parameter let's you overwrite the value provided in
|
|
31
|
+
* oidcEarlyInit({ BASE_URL: xxx });
|
|
32
|
+
*
|
|
33
|
+
* What should you put in this parameter?
|
|
34
|
+
* - Vite project: `BASE_URL: import.meta.env.BASE_URL`
|
|
35
|
+
* - Create React App project: `BASE_URL: process.env.PUBLIC_URL`
|
|
36
|
+
* - Other: `BASE_URL: "/"` (Usually, or `/dashboard` if your app is not at the root of the domain)
|
|
37
|
+
*/
|
|
26
38
|
BASE_URL?: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Determines how session restoration is handled.
|
|
42
|
+
* Session restoration allows users to stay logged in between visits
|
|
43
|
+
* without needing to explicitly sign in each time.
|
|
44
|
+
*
|
|
45
|
+
* Options:
|
|
46
|
+
*
|
|
47
|
+
* - **"auto" (default)**:
|
|
48
|
+
* Automatically selects the best method.
|
|
49
|
+
* If the app’s domain shares a common parent domain with the authorization endpoint,
|
|
50
|
+
* an iframe is used for silent session restoration.
|
|
51
|
+
* Otherwise, a full-page redirect is used.
|
|
52
|
+
*
|
|
53
|
+
* - **"full page redirect"**:
|
|
54
|
+
* Forces full-page reloads for session restoration.
|
|
55
|
+
* Use this if your application is served with a restrictive CSP
|
|
56
|
+
* (e.g., `Content-Security-Policy: frame-ancestors "none"`)
|
|
57
|
+
* or `X-Frame-Options: DENY`, and you cannot modify those headers.
|
|
58
|
+
* This mode provides a slightly less seamless UX and will lead oidc-spa to
|
|
59
|
+
* store tokens in `localStorage` if multiple OIDC clients are used
|
|
60
|
+
* (e.g., your app communicates with several APIs).
|
|
61
|
+
*
|
|
62
|
+
* - **"iframe"**:
|
|
63
|
+
* Forces iframe-based session restoration.
|
|
64
|
+
* In development, if you go in your browser setting and allow your auth server’s domain
|
|
65
|
+
* to set third-party cookies this value will let you test your app
|
|
66
|
+
* with the local dev server as it will behave in production.
|
|
67
|
+
*/
|
|
68
|
+
sessionRestorationMethod?: "iframe" | "full page redirect" | "auto";
|
|
27
69
|
};
|
|
28
70
|
|
|
29
71
|
/**
|
|
@@ -99,7 +141,8 @@ export class Keycloak {
|
|
|
99
141
|
let hasCreateResolved = false;
|
|
100
142
|
|
|
101
143
|
const oidcOrError = await createOidc({
|
|
102
|
-
|
|
144
|
+
BASE_URL: constructorParams.BASE_URL,
|
|
145
|
+
sessionRestorationMethod: constructorParams.sessionRestorationMethod,
|
|
103
146
|
issuerUri,
|
|
104
147
|
clientId: this.#state.constructorParams.clientId,
|
|
105
148
|
autoLogin,
|
|
@@ -892,11 +935,21 @@ export class Keycloak {
|
|
|
892
935
|
createAccountUrl(options?: KeycloakAccountOptions & { locale?: string }): string {
|
|
893
936
|
const { locale, redirectUri } = options ?? {};
|
|
894
937
|
|
|
895
|
-
const { keycloakUtils } = this.#state;
|
|
938
|
+
const { keycloakUtils, constructorParams } = this.#state;
|
|
896
939
|
|
|
897
940
|
return keycloakUtils.getAccountUrl({
|
|
898
941
|
clientId: this.clientId,
|
|
899
|
-
|
|
942
|
+
validRedirectUri: (() => {
|
|
943
|
+
if (redirectUri !== undefined) {
|
|
944
|
+
return redirectUri;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const { homeUrlAndRedirectUri } = getHomeAndRedirectUri({
|
|
948
|
+
BASE_URL_params: constructorParams.BASE_URL
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
return homeUrlAndRedirectUri;
|
|
952
|
+
})(),
|
|
900
953
|
locale
|
|
901
954
|
});
|
|
902
955
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { toFullyQualifiedUrl } from "../tools/toFullyQualifiedUrl";
|
|
2
2
|
import { type KeycloakIssuerUriParsed, parseKeycloakIssuerUri } from "./keycloakIssuerUriParsed";
|
|
3
|
+
import { assert } from "../tools/tsafe/assert";
|
|
3
4
|
|
|
4
5
|
export type KeycloakUtils = {
|
|
5
6
|
issuerUriParsed: KeycloakIssuerUriParsed;
|
|
@@ -7,7 +8,9 @@ export type KeycloakUtils = {
|
|
|
7
8
|
adminConsoleUrl_master: string;
|
|
8
9
|
getAccountUrl: (params: {
|
|
9
10
|
clientId: string;
|
|
10
|
-
|
|
11
|
+
validRedirectUri?: string;
|
|
12
|
+
/** @deprecated: Use validRedirectUri */
|
|
13
|
+
backToAppFromAccountUrl?: string;
|
|
11
14
|
locale?: string;
|
|
12
15
|
}) => string;
|
|
13
16
|
fetchUserProfile: (params: { accessToken: string }) => Promise<KeycloakProfile>;
|
|
@@ -49,17 +52,46 @@ export function createKeycloakUtils(params: { issuerUri: string }): KeycloakUtil
|
|
|
49
52
|
issuerUriParsed,
|
|
50
53
|
adminConsoleUrl: getAdminConsoleUrl(issuerUriParsed.realm),
|
|
51
54
|
adminConsoleUrl_master: getAdminConsoleUrl("master"),
|
|
52
|
-
getAccountUrl: ({ clientId,
|
|
55
|
+
getAccountUrl: ({ clientId, locale, ...rest }) => {
|
|
56
|
+
const validRedirectUri = (() => {
|
|
57
|
+
const { validRedirectUri, backToAppFromAccountUrl } = rest;
|
|
58
|
+
|
|
59
|
+
if (validRedirectUri !== undefined) {
|
|
60
|
+
assert(
|
|
61
|
+
backToAppFromAccountUrl === undefined,
|
|
62
|
+
"getAccountUrl: backToAppFromAccountUrl is deprecated"
|
|
63
|
+
);
|
|
64
|
+
return validRedirectUri;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
assert(
|
|
68
|
+
backToAppFromAccountUrl !== undefined,
|
|
69
|
+
"getAccountUrl: Must provide validRedirectUri"
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return backToAppFromAccountUrl;
|
|
73
|
+
})();
|
|
74
|
+
|
|
53
75
|
const accountUrlObj = new URL(
|
|
54
76
|
`${keycloakServerUrl}/realms/${issuerUriParsed.realm}/account`
|
|
55
77
|
);
|
|
56
78
|
accountUrlObj.searchParams.set("referrer", clientId);
|
|
57
79
|
accountUrlObj.searchParams.set(
|
|
58
80
|
"referrer_uri",
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
81
|
+
(() => {
|
|
82
|
+
try {
|
|
83
|
+
return toFullyQualifiedUrl({
|
|
84
|
+
urlish: validRedirectUri,
|
|
85
|
+
doAssertNoQueryParams: true,
|
|
86
|
+
doOutputWithTrailingSlash: true
|
|
87
|
+
});
|
|
88
|
+
} catch {
|
|
89
|
+
return toFullyQualifiedUrl({
|
|
90
|
+
urlish: validRedirectUri,
|
|
91
|
+
doAssertNoQueryParams: false
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
})()
|
|
63
95
|
);
|
|
64
96
|
if (locale !== undefined) {
|
|
65
97
|
accountUrlObj.searchParams.set("kc_locale", locale);
|
package/src/mock/oidc.ts
CHANGED
|
@@ -96,7 +96,8 @@ export async function createMockOidc<
|
|
|
96
96
|
const common: Oidc.Common = {
|
|
97
97
|
params: {
|
|
98
98
|
clientId: mockedParams.clientId ?? "mymockclient",
|
|
99
|
-
issuerUri: mockedParams.issuerUri ?? "https://my-mock-oidc-server.net/realms/mymockrealm"
|
|
99
|
+
issuerUri: mockedParams.issuerUri ?? "https://my-mock-oidc-server.net/realms/mymockrealm",
|
|
100
|
+
validRedirectUri: homeUrl
|
|
100
101
|
}
|
|
101
102
|
};
|
|
102
103
|
|