@axa-fr/react-oidc 5.13.12 → 6.0.0-alpha1

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 (67) hide show
  1. package/README.md +1 -0
  2. package/dist/OidcProvider.d.ts +1 -0
  3. package/dist/OidcProvider.d.ts.map +1 -1
  4. package/dist/OidcProvider.js +13 -5
  5. package/dist/OidcProvider.js.map +1 -1
  6. package/dist/OidcServiceWorker.js +29 -1
  7. package/dist/OidcTrustedDomains.js +7 -4
  8. package/dist/ReactOidc.d.ts.map +1 -1
  9. package/dist/ReactOidc.js +29 -7
  10. package/dist/ReactOidc.js.map +1 -1
  11. package/dist/core/default-component/ServiceWorkerInstall.component.d.ts.map +1 -1
  12. package/dist/core/default-component/ServiceWorkerInstall.component.js +21 -9
  13. package/dist/core/default-component/ServiceWorkerInstall.component.js.map +1 -1
  14. package/dist/core/default-component/SilentCallback.component.d.ts.map +1 -1
  15. package/dist/core/default-component/SilentCallback.component.js +23 -15
  16. package/dist/core/default-component/SilentCallback.component.js.map +1 -1
  17. package/dist/core/default-component/SilentSignin.component.d.ts +4 -0
  18. package/dist/core/default-component/SilentSignin.component.d.ts.map +1 -0
  19. package/dist/core/default-component/SilentSignin.component.js +58 -0
  20. package/dist/core/default-component/SilentSignin.component.js.map +1 -0
  21. package/dist/core/routes/OidcRoutes.d.ts +1 -0
  22. package/dist/core/routes/OidcRoutes.d.ts.map +1 -1
  23. package/dist/core/routes/OidcRoutes.js +8 -2
  24. package/dist/core/routes/OidcRoutes.js.map +1 -1
  25. package/dist/vanilla/checkSessionIFrame.d.ts +17 -0
  26. package/dist/vanilla/checkSessionIFrame.d.ts.map +1 -0
  27. package/dist/vanilla/checkSessionIFrame.js +78 -0
  28. package/dist/vanilla/checkSessionIFrame.js.map +1 -0
  29. package/dist/vanilla/initSession.d.ts +3 -1
  30. package/dist/vanilla/initSession.d.ts.map +1 -1
  31. package/dist/vanilla/initSession.js +21 -11
  32. package/dist/vanilla/initSession.js.map +1 -1
  33. package/dist/vanilla/initWorker.d.ts +4 -0
  34. package/dist/vanilla/initWorker.d.ts.map +1 -1
  35. package/dist/vanilla/initWorker.js +31 -3
  36. package/dist/vanilla/initWorker.js.map +1 -1
  37. package/dist/vanilla/oidc.d.ts +25 -8
  38. package/dist/vanilla/oidc.d.ts.map +1 -1
  39. package/dist/vanilla/oidc.js +547 -263
  40. package/dist/vanilla/oidc.js.map +1 -1
  41. package/dist/vanilla/route-utils.d.ts +13 -0
  42. package/dist/vanilla/route-utils.d.ts.map +1 -0
  43. package/dist/vanilla/route-utils.js +65 -0
  44. package/dist/vanilla/route-utils.js.map +1 -0
  45. package/package.json +1 -1
  46. package/src/App.tsx +1 -1
  47. package/src/MultiAuth.tsx +2 -2
  48. package/src/configurations.ts +8 -3
  49. package/src/oidc/OidcProvider.tsx +11 -0
  50. package/src/oidc/ReactOidc.tsx +32 -8
  51. package/src/oidc/core/default-component/ServiceWorkerInstall.component.tsx +15 -3
  52. package/src/oidc/core/default-component/SilentCallback.component.tsx +10 -15
  53. package/src/oidc/core/default-component/SilentSignin.component.tsx +35 -0
  54. package/src/oidc/core/routes/OidcRoutes.tsx +10 -1
  55. package/src/oidc/vanilla/OidcServiceWorker.js +29 -1
  56. package/src/oidc/vanilla/OidcTrustedDomains.js +7 -4
  57. package/src/oidc/vanilla/checkSessionIFrame.ts +82 -0
  58. package/src/oidc/vanilla/initSession.ts +23 -11
  59. package/src/oidc/vanilla/initWorker.ts +19 -2
  60. package/src/oidc/vanilla/oidc.ts +416 -163
  61. package/src/oidc/{core/routes → vanilla}/route-utils.spec.ts +0 -0
  62. package/src/oidc/vanilla/route-utils.ts +76 -0
  63. package/dist/core/routes/route-utils.d.ts +0 -2
  64. package/dist/core/routes/route-utils.d.ts.map +0 -1
  65. package/dist/core/routes/route-utils.js +0 -32
  66. package/dist/core/routes/route-utils.js.map +0 -1
  67. package/src/oidc/core/routes/route-utils.ts +0 -34
@@ -10,18 +10,32 @@ import {
10
10
  RedirectRequestHandler,
11
11
  TokenRequest
12
12
  } from '@openid/appauth';
13
- import {NoHashQueryStringUtils, HashQueryStringUtils} from './noHashQueryStringUtils';
13
+ import {HashQueryStringUtils, NoHashQueryStringUtils} from './noHashQueryStringUtils';
14
14
  import {initWorkerAsync, sleepAsync} from './initWorker'
15
15
  import {MemoryStorageBackend} from "./memoryStorageBackend";
16
16
  import {initSession} from "./initSession";
17
17
  import timer from './timer';
18
18
 
19
- const isInIframe = () => {
20
- try {
21
- return window.self !== window.top;
22
- } catch (e) {
23
- return true;
19
+ import {CheckSessionIFrame} from "./checkSessionIFrame"
20
+ import {getParseQueryStringFromLocation} from "./route-utils";
21
+ import {AuthorizationServiceConfigurationJson} from "@openid/appauth/src/authorization_service_configuration";
22
+
23
+ export interface OidcAuthorizationServiceConfigurationJson extends AuthorizationServiceConfigurationJson{
24
+ check_session_iframe?: string;
25
+ }
26
+
27
+ export class OidcAuthorizationServiceConfiguration extends AuthorizationServiceConfiguration{
28
+ private check_session_iframe: string;
29
+
30
+ constructor(request: any) {
31
+ super(request);
32
+ this.authorizationEndpoint = request.authorization_endpoint;
33
+ this.tokenEndpoint = request.token_endpoint;
34
+ this.revocationEndpoint = request.revocation_endpoint;
35
+ this.userInfoEndpoint = request.userinfo_endpoint;
36
+ this.check_session_iframe = request.check_session_iframe;
24
37
  }
38
+
25
39
  }
26
40
 
27
41
  const idTokenPayload = (token) => {
@@ -70,23 +84,27 @@ export interface AuthorityConfiguration {
70
84
  revocation_endpoint: string;
71
85
  end_session_endpoint?: string;
72
86
  userinfo_endpoint?: string;
87
+ check_session_iframe?:string;
73
88
  }
74
89
 
75
- const refresh_token_scope = "offline_access";
76
90
  export type OidcConfiguration = {
77
- client_id: string,
78
- redirect_uri: string,
91
+ client_id: string,
92
+ redirect_uri: string,
79
93
  silent_redirect_uri?:string,
94
+ silent_signin_uri?:string,
80
95
  silent_signin_timeout?:number,
81
- scope: string,
82
- authority: string,
96
+ scope: string,
97
+ authority: string,
98
+ authority_time_cache_wellknowurl_in_second?: number,
83
99
  authority_configuration?: AuthorityConfiguration,
84
- refresh_time_before_tokens_expiration_in_second?: number,
85
- token_request_timeout?: number,
86
- service_worker_relative_url?:string,
100
+ refresh_time_before_tokens_expiration_in_second?: number,
101
+ token_request_timeout?: number,
102
+ service_worker_relative_url?:string,
87
103
  service_worker_only?:boolean,
88
104
  extras?:StringMap
89
- token_request_extras?:StringMap,
105
+ token_request_extras?:StringMap,
106
+ storage?: Storage
107
+ monitor_session?: boolean
90
108
  };
91
109
 
92
110
  const oidcDatabase = {};
@@ -99,22 +117,16 @@ const oidcFactory = (configuration: OidcConfiguration, name="default") => {
99
117
  }
100
118
 
101
119
  const loginCallbackWithAutoTokensRenewAsync = async (oidc) => {
102
- const response = await oidc.loginCallbackAsync();
103
- const tokens = response.tokens
104
- oidc.tokens = await setTokensAsync(oidc.serviceWorker, tokens);
105
- if(!oidc.serviceWorker){
106
- await oidc.session.setTokens(oidc.tokens);
107
- }
108
- oidc.publishEvent(Oidc.eventNames.token_aquired, oidc.tokens);
109
- oidc.timeoutId = autoRenewTokens(oidc, tokens.refreshToken, oidc.tokens.expiresAt)
110
- return { state:response.state, callbackPath : response.callbackPath };
120
+ const { parsedTokens, state, callbackPath } = await oidc.loginCallbackAsync();
121
+ oidc.timeoutId = autoRenewTokens(oidc, parsedTokens.refreshToken, parsedTokens.expiresAt)
122
+ return { state, callbackPath };
111
123
  }
112
124
 
113
125
  const autoRenewTokens = (oidc, refreshToken, expiresAt) => {
114
126
  const refreshTimeBeforeTokensExpirationInSecond = oidc.configuration.refresh_time_before_tokens_expiration_in_second ?? 60;
115
127
  return timer.setTimeout(async () => {
116
128
  const currentTimeUnixSecond = new Date().getTime() /1000;
117
- const timeInfo = { timeLeft: Math.round(((expiresAt - refreshTimeBeforeTokensExpirationInSecond)- currentTimeUnixSecond))};
129
+ const timeInfo = { timeLeft: Math.round(((expiresAt - refreshTimeBeforeTokensExpirationInSecond) - currentTimeUnixSecond))};
118
130
  oidc.publishEvent(Oidc.eventNames.token_timer, timeInfo);
119
131
  if(currentTimeUnixSecond > (expiresAt - refreshTimeBeforeTokensExpirationInSecond)) {
120
132
  const tokens = await oidc.refreshTokensAsync(refreshToken);
@@ -123,9 +135,13 @@ const autoRenewTokens = (oidc, refreshToken, expiresAt) => {
123
135
  await oidc.session.setTokens(oidc.tokens);
124
136
  }
125
137
  if(!oidc.tokens){
138
+ if(oidc.checkSessionIFrame){
139
+ oidc.checkSessionIFrame.stop();
140
+ oidc.checkSessionIFrame = null;
141
+ }
126
142
  return;
127
143
  }
128
- oidc.publishEvent(Oidc.eventNames.token_renewed, oidc.tokens);
144
+ oidc.publishEvent(Oidc.eventNames.token_renewed, {});
129
145
  if(oidc.timeoutId) {
130
146
  oidc.timeoutId = autoRenewTokens(oidc, tokens.refreshToken, oidc.tokens.expiresAt);
131
147
  }
@@ -138,10 +154,14 @@ const autoRenewTokens = (oidc, refreshToken, expiresAt) => {
138
154
  }, 1000);
139
155
  }
140
156
 
141
- export const getLoginParams = (configurationName) => {
142
- return JSON.parse(sessionStorage[`oidc_login.${configurationName}`]);
157
+ const getLoginSessionKey = (configurationName:string, redirectUri:string) => {
158
+ return `oidc_login.${configurationName}:${redirectUri}`;
159
+ }
160
+ const getLoginParams = (configurationName, redirectUri) => {
161
+ return JSON.parse(sessionStorage[getLoginSessionKey(configurationName, redirectUri)]);
143
162
  }
144
163
 
164
+
145
165
  const userInfoAsync = async (oidc) => {
146
166
  if(oidc.userInfo != null){
147
167
  return oidc.userInfo;
@@ -188,13 +208,15 @@ const setTokensAsync = async (serviceWorker, tokens) =>{
188
208
  else {
189
209
  accessTokenPayload = extractAccessTokenPayload(tokens);
190
210
  }
191
- const expiresAt = tokens.issuedAt + tokens.expiresIn;
192
- return {...tokens, idTokenPayload: idTokenPayload(tokens.idToken), accessTokenPayload, expiresAt};
211
+ const _idTokenPayload = idTokenPayload(tokens.idToken);
212
+ const expiresAt = (_idTokenPayload && _idTokenPayload.exp) ? _idTokenPayload.exp : tokens.issuedAt + tokens.expiresIn;
213
+ return {...tokens, idTokenPayload: _idTokenPayload, accessTokenPayload, expiresAt};
193
214
  }
194
215
 
195
216
  const eventNames = {
196
217
  service_worker_not_supported_by_browser: "service_worker_not_supported_by_browser",
197
218
  token_aquired: "token_aquired",
219
+ logout_from_another_tab: "logout_from_another_tab",
198
220
  token_renewed: "token_renewed",
199
221
  token_timer: "token_timer",
200
222
  loginAsync_begin:"loginAsync_begin",
@@ -205,8 +227,6 @@ const eventNames = {
205
227
  refreshTokensAsync_begin: "refreshTokensAsync_begin",
206
228
  refreshTokensAsync_end: "refreshTokensAsync_end",
207
229
  refreshTokensAsync_error: "refreshTokensAsync_error",
208
- refreshTokensAsync_silent_begin: "refreshTokensAsync_silent_begin",
209
- refreshTokensAsync_silent_end: "refreshTokensAsync_silent_end",
210
230
  refreshTokensAsync_silent_error: "refreshTokensAsync_silent_error",
211
231
  tryKeepExistingSessionAsync_begin: "tryKeepExistingSessionAsync_begin",
212
232
  tryKeepExistingSessionAsync_end: "tryKeepExistingSessionAsync_end",
@@ -225,6 +245,51 @@ const getRandomInt = (max) => {
225
245
  return Math.floor(Math.random() * max);
226
246
  }
227
247
 
248
+ const WELL_KNOWN_PATH = '.well-known';
249
+ const OPENID_CONFIGURATION = 'openid-configuration';
250
+
251
+
252
+ const oneHourSecond = 60 * 60;
253
+ const fetchFromIssuer = async (openIdIssuerUrl: string, timeCacheSecond = oneHourSecond):
254
+ Promise<OidcAuthorizationServiceConfiguration> => {
255
+ const fullUrl = `${openIdIssuerUrl}/${WELL_KNOWN_PATH}/${OPENID_CONFIGURATION}`;
256
+
257
+ const localStorageKey = `oidc.server:${openIdIssuerUrl}`;
258
+ const cacheJson = window.localStorage.getItem(localStorageKey);
259
+
260
+ const oneHourMinisecond = 1000 * timeCacheSecond;
261
+ // @ts-ignore
262
+ if(cacheJson && (cacheJson.timestamp + oneHourMinisecond) > Date.now()){
263
+ return new OidcAuthorizationServiceConfiguration(JSON.parse(cacheJson));
264
+ }
265
+
266
+ const res = await fetch(fullUrl);
267
+
268
+ if (res.status != 200) {
269
+ return null;
270
+ }
271
+
272
+
273
+ const result = await res.json();
274
+ window.localStorage.setItem(localStorageKey, JSON.stringify({result, timestamp:Date.now()}));
275
+
276
+ return new OidcAuthorizationServiceConfiguration(result);
277
+ }
278
+
279
+ const buildQueries = (extras:StringMap) => {
280
+ let queries = '';
281
+ if(extras != null){
282
+ for (let [key, value] of Object.entries(extras)) {
283
+ if (queries === ""){
284
+ queries = `?${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
285
+ } else {
286
+ queries+= `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
287
+ }
288
+ }
289
+ }
290
+ return queries;
291
+ }
292
+
228
293
  export class Oidc {
229
294
  public configuration: OidcConfiguration;
230
295
  public userInfo: null;
@@ -234,7 +299,7 @@ export class Oidc {
234
299
  private serviceWorker?: any;
235
300
  private configurationName: string;
236
301
  private session?: any;
237
- private iFrameSession= {}
302
+ private checkSessionIFrame: CheckSessionIFrame;
238
303
  constructor(configuration:OidcConfiguration, configurationName="default") {
239
304
  this.configuration = configuration
240
305
  this.configurationName= configurationName;
@@ -248,6 +313,7 @@ export class Oidc {
248
313
  this.loginCallbackWithAutoTokensRenewAsync.bind(this);
249
314
  this.initAsync.bind(this);
250
315
  this.loginCallbackAsync.bind(this);
316
+ this._loginCallbackAsync.bind(this);
251
317
  this.subscriveEvents.bind(this);
252
318
  this.removeEventSubscription.bind(this);
253
319
  this.publishEvent.bind(this);
@@ -285,11 +351,18 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
285
351
 
286
352
  silentSigninCallbackFromIFrame(){
287
353
  if (this.configuration.silent_redirect_uri) {
288
- window.top.postMessage(`${this.configurationName}_oidc_tokens:${JSON.stringify(this.tokens)}`, window.location.origin);
354
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
355
+ window.top.postMessage(`${this.configurationName}_oidc_tokens:${JSON.stringify({tokens:this.tokens, sessionState:queryParams.session_state})}`, window.location.origin);
289
356
  }
290
357
  }
291
- async silentSigninAsync() {
292
- if (!this.configuration.silent_redirect_uri) {
358
+ silentSigninErrorCallbackFromIFrame(){
359
+ if (this.configuration.silent_redirect_uri) {
360
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
361
+ window.top.postMessage(`${this.configurationName}_oidc_error:${JSON.stringify({error:queryParams.error})}`, window.location.origin);
362
+ }
363
+ }
364
+ async silentSigninAsync(extras:StringMap=null, state:string=null, scope:string=null) {
365
+ if (!this.configuration.silent_redirect_uri || !this.configuration.silent_signin_uri) {
293
366
  return Promise.resolve(null);
294
367
  }
295
368
  while (document.hidden) {
@@ -300,10 +373,38 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
300
373
  try {
301
374
  this.publishEvent(eventNames.silentSigninAsync_begin, {});
302
375
  const configuration = this.configuration
303
- const link = configuration.silent_redirect_uri;
376
+ let queries = "";
377
+
378
+ if(state){
379
+ if(extras == null){
380
+ extras = {};
381
+ }
382
+ extras.state = state;
383
+ }
384
+
385
+ if(scope){
386
+ if(extras == null){
387
+ extras = {};
388
+ }
389
+ extras.scope = scope;
390
+ }
391
+
392
+ if(extras != null){
393
+ for (let [key, value] of Object.entries(extras)) {
394
+ if (queries === ""){
395
+ queries = `?${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
396
+ } else {
397
+ queries+= `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
398
+ }
399
+ }
400
+ }
401
+ const link = configuration.silent_signin_uri + queries;
402
+ const idx = link.indexOf("/", link.indexOf("//") + 2);
403
+ const iFrameOrigin = link.substr(0, idx);
304
404
  const iframe = document.createElement('iframe');
305
405
  iframe.width = "0px";
306
406
  iframe.height = "0px";
407
+
307
408
  iframe.id = `${this.configurationName}_oidc_iframe`;
308
409
  iframe.setAttribute("src", link);
309
410
  document.body.appendChild(iframe);
@@ -312,18 +413,33 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
312
413
  try {
313
414
  let isResolved = false;
314
415
  window.onmessage = function (e) {
315
- const key = `${self.configurationName}_oidc_tokens:`;
316
- if (e.data && typeof (e.data) === "string" && e.data.startsWith(key)) {
317
- if (!isResolved) {
318
- const result = JSON.parse(e.data.replace(key, ''));
319
- self.publishEvent(eventNames.silentSigninAsync_end, result);
320
- iframe.remove();
321
- isResolved = true;
322
- resolve(result);
416
+ if (e.origin === iFrameOrigin &&
417
+ e.source === iframe.contentWindow
418
+ ) {
419
+ const key = `${self.configurationName}_oidc_tokens:`;
420
+ const key_error = `${self.configurationName}_oidc_error:`;
421
+ const data = e.data;
422
+ if (data && typeof (data) === "string") {
423
+ if (!isResolved) {
424
+ if(data.startsWith(key)) {
425
+ const result = JSON.parse(e.data.replace(key, ''));
426
+ self.publishEvent(eventNames.silentSigninAsync_end, {});
427
+ iframe.remove();
428
+ isResolved = true;
429
+ resolve(result);
430
+ }
431
+ else if(data.startsWith(key_error)) {
432
+ const result = JSON.parse(e.data.replace(key_error, ''));
433
+ self.publishEvent(eventNames.silentSigninAsync_error, result);
434
+ iframe.remove();
435
+ isResolved = true;
436
+ reject(result);
437
+ }
438
+ }
323
439
  }
324
440
  }
325
441
  };
326
- const silentSigninTimeout = configuration.silent_signin_timeout ? configuration.silent_signin_timeout : 12000
442
+ const silentSigninTimeout = configuration.silent_signin_timeout ?? 12000
327
443
  setTimeout(() => {
328
444
  if (!isResolved) {
329
445
  self.publishEvent(eventNames.silentSigninAsync_error, "timeout");
@@ -346,17 +462,20 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
346
462
  initAsyncPromise = null;
347
463
  async initAsync(authority:string, authorityConfiguration:AuthorityConfiguration) {
348
464
  if (authorityConfiguration != null) {
349
- return new AuthorizationServiceConfiguration( {
465
+ return new OidcAuthorizationServiceConfiguration( {
350
466
  authorization_endpoint: authorityConfiguration.authorization_endpoint,
351
467
  end_session_endpoint: authorityConfiguration.end_session_endpoint,
352
468
  revocation_endpoint: authorityConfiguration.revocation_endpoint,
353
469
  token_endpoint: authorityConfiguration.token_endpoint,
354
- userinfo_endpoint: authorityConfiguration.userinfo_endpoint});
470
+ userinfo_endpoint: authorityConfiguration.userinfo_endpoint,
471
+ check_session_iframe:authorityConfiguration.check_session_iframe,
472
+ });
355
473
  }
356
474
  if(this.initAsyncPromise){
357
475
  return this.initAsyncPromise;
358
476
  }
359
- this.initAsyncPromise = await AuthorizationServiceConfiguration.fetchFromIssuer(authority, new FetchRequestor());
477
+
478
+ this.initAsyncPromise = await fetchFromIssuer(authority, this.configuration.authority_time_cache_wellknowurl_in_second ?? 60 * 60);
360
479
  return this.initAsyncPromise;
361
480
  }
362
481
 
@@ -380,12 +499,21 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
380
499
  const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "tryKeepExistingSessionAsync");
381
500
  if (tokens) {
382
501
  serviceWorker.startKeepAliveServiceWorker();
383
- const updatedTokens = await this.refreshTokensAsync(tokens.refresh_token, true);
384
502
  // @ts-ignore
385
- this.tokens = await setTokensAsync(serviceWorker, updatedTokens);
503
+ const reformattedToken = {
504
+ accessToken : tokens.access_token,
505
+ refreshToken : tokens.refresh_token,
506
+ expiresIn: tokens.expires_in,
507
+ idToken: tokens.id_token,
508
+ scope: tokens.scope,
509
+ tokenType: tokens.token_type
510
+ }
511
+ this.tokens = await setTokensAsync(serviceWorker, reformattedToken);
386
512
  this.serviceWorker = serviceWorker;
387
513
  // @ts-ignore
388
- this.timeoutId = autoRenewTokens(this, updatedTokens.refreshToken, this.tokens.expiresAt);
514
+ this.timeoutId = autoRenewTokens(this, this.tokens.refreshToken, this.tokens.expiresAt);
515
+ const sessionState = await serviceWorker.getSessionStateAsync();
516
+ await this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
389
517
  this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
390
518
  success: true,
391
519
  message: "tokens inside ServiceWorker are valid"
@@ -402,19 +530,22 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
402
530
  message: "service worker is not supported by this browser"
403
531
  });
404
532
  }
405
- const session = initSession(this.configurationName);
533
+ const session = initSession(this.configurationName, configuration.redirect_uri, configuration.storage ?? sessionStorage);
406
534
  const {tokens} = await session.initAsync();
535
+ console.log("const {tokens} = await session.initAsync();")
536
+ console.log(tokens)
407
537
  if (tokens) {
408
- const updatedTokens = await this.refreshTokensAsync(tokens.refreshToken, true);
409
538
  // @ts-ignore
410
- this.tokens = await setTokensAsync(serviceWorker, updatedTokens);
411
- session.setTokens(this.tokens);
539
+ this.tokens = await setTokensAsync(serviceWorker, tokens);
540
+ //session.setTokens(this.tokens);
412
541
  this.session = session;
413
542
  // @ts-ignore
414
- this.timeoutId = autoRenewTokens(this, updatedTokens.refreshToken, this.tokens.expiresAt);
543
+ this.timeoutId = autoRenewTokens(this, tokens.refreshToken, this.tokens.expiresAt);
544
+ const sessionState = session.getSessionState();
545
+ await this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
415
546
  this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
416
547
  success: true,
417
- message: "tokens inside ServiceWorker are valid"
548
+ message: `tokens inside storage are valid`
418
549
  });
419
550
  return true;
420
551
  }
@@ -425,6 +556,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
425
556
  });
426
557
  return false;
427
558
  } catch (exception) {
559
+ console.error(exception);
428
560
  if (serviceWorker) {
429
561
  await serviceWorker.clearAsync();
430
562
  }
@@ -440,109 +572,167 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
440
572
  });
441
573
  }
442
574
 
443
- async loginAsync(callbackPath:string=undefined, extras:StringMap=null, installServiceWorker=true, state:string=undefined) {
444
- try {
445
- const location = window.location;
446
- const url = callbackPath || location.pathname + (location.search || '') + (location.hash || '');
447
- this.publishEvent(eventNames.loginAsync_begin, {});
448
- const configuration = this.configuration
449
- // Security we cannot loggin from Iframe
450
- if (!configuration.silent_redirect_uri && isInIframe()) {
451
- throw new Error("Login from iframe is forbidden");
452
- }
453
- sessionStorage[`oidc_login.${this.configurationName}`] = JSON.stringify({callbackPath:url,extras,state});
454
-
455
- let serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
456
- const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
457
- if(serviceWorker && installServiceWorker) {
458
- const isServiceWorkerProxyActive = await serviceWorker.isServiceWorkerProxyActiveAsync();
459
- if(!isServiceWorkerProxyActive) {
460
- window.location.href = `${configuration.redirect_uri}/service-worker-install`;
461
- return;
575
+ loginPromise: Promise<any>=null;
576
+ async loginAsync(callbackPath:string=undefined, extras:StringMap=null, installServiceWorker=true, state:string=undefined, isSilentSignin:boolean=false, scope:string=undefined) {
577
+ if(this.loginPromise !== null){
578
+ return this.loginPromise;
579
+ }
580
+
581
+ const loginLocalAsync=async () => {
582
+ try {
583
+ const location = window.location;
584
+ const url = callbackPath || location.pathname + (location.search || '') + (location.hash || '');
585
+ this.publishEvent(eventNames.loginAsync_begin, {});
586
+ const configuration = this.configuration;
587
+
588
+ const redirectUri = isSilentSignin ? configuration.silent_redirect_uri : configuration.redirect_uri;
589
+ if (!scope) {
590
+ scope = configuration.scope;
591
+ }
592
+
593
+ const sessionKey = getLoginSessionKey(this.configurationName, redirectUri);
594
+ sessionStorage[sessionKey] = JSON.stringify({callbackPath: url, extras, state});
595
+
596
+ let serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
597
+ const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
598
+ if (serviceWorker && installServiceWorker) {
599
+ const isServiceWorkerProxyActive = await serviceWorker.isServiceWorkerProxyActiveAsync();
600
+ if (!isServiceWorkerProxyActive) {
601
+ const isUnregistered = await serviceWorker.unregisterAsync();
602
+ const extrasQueries = extras != null ? {...extras}: {};
603
+ extrasQueries.callbackPath = url;
604
+ extrasQueries.state = state;
605
+ const queryString = buildQueries(extrasQueries);
606
+ window.location.href = `${redirectUri}/service-worker-install${queryString}`;
607
+ return;
608
+ }
609
+ }
610
+ let storage;
611
+ if (serviceWorker) {
612
+ serviceWorker.startKeepAliveServiceWorker();
613
+ await serviceWorker.initAsync(oidcServerConfiguration, "loginAsync");
614
+ storage = new MemoryStorageBackend(serviceWorker.saveItemsAsync, {});
615
+ await storage.setItem("dummy", {});
616
+ } else {
617
+ const session = initSession(this.configurationName, redirectUri);
618
+ storage = new MemoryStorageBackend(session.saveItemsAsync, {});
462
619
  }
620
+
621
+ const extraFinal = extras ?? configuration.extras ?? {};
622
+
623
+ // @ts-ignore
624
+ const queryStringUtil = redirectUri.includes("#") ? new HashQueryStringUtils() : new NoHashQueryStringUtils();
625
+ const authorizationHandler = new RedirectRequestHandler(storage, queryStringUtil, window.location, new DefaultCrypto());
626
+ const authRequest = new AuthorizationRequest({
627
+ client_id: configuration.client_id,
628
+ redirect_uri: redirectUri,
629
+ scope,
630
+ response_type: AuthorizationRequest.RESPONSE_TYPE_CODE,
631
+ state,
632
+ extras: extraFinal
633
+ });
634
+ authorizationHandler.performAuthorizationRequest(oidcServerConfiguration, authRequest);
635
+ } catch (exception) {
636
+ this.publishEvent(eventNames.loginAsync_error, exception);
637
+ throw exception;
463
638
  }
464
- let storage;
465
- if(serviceWorker) {
466
- serviceWorker.startKeepAliveServiceWorker();
467
- await serviceWorker.initAsync(oidcServerConfiguration, "loginAsync");
468
- storage = new MemoryStorageBackend(serviceWorker.saveItemsAsync, {});
469
- await storage.setItem("dummy",{});
639
+ }
640
+ this.loginPromise = loginLocalAsync();
641
+ return this.loginPromise.then(result =>{
642
+ this.loginPromise = null;
643
+ return result;
644
+ });
645
+
646
+ }
647
+
648
+ async startCheckSessionAsync(checkSessionIFrameUri, clientId, sessionState, isSilentSignin=false){
649
+ return new Promise((resolve:Function, reject) => {
650
+ if (this.configuration.silent_signin_uri && this.configuration.silent_redirect_uri && this.configuration.monitor_session && checkSessionIFrameUri && sessionState && !isSilentSignin) {
651
+ const checkSessionCallback = () => {
652
+ this.checkSessionIFrame.stop();
653
+
654
+ if(this.tokens === null){
655
+ return;
656
+ }
657
+ // @ts-ignore
658
+ const idToken = this.tokens.idToken;
659
+ // @ts-ignore
660
+ const idTokenPayload = this.tokens.idTokenPayload;
661
+ this.silentSigninAsync({
662
+ prompt: "none",
663
+ id_token_hint: idToken,
664
+ scope: "openid"
665
+ }).then((silentSigninResponse) => {
666
+ const iFrameIdTokenPayload = silentSigninResponse.tokens.idTokenPayload;
667
+ if (idTokenPayload.sub === iFrameIdTokenPayload.sub) {
668
+ const sessionState = silentSigninResponse.sessionState;
669
+ this.checkSessionIFrame.start(silentSigninResponse.sessionState);
670
+ if (idTokenPayload.sid === iFrameIdTokenPayload.sid) {
671
+ console.debug("SessionMonitor._callback: Same sub still logged in at OP, restarting check session iframe; session_state:", sessionState);
672
+ } else {
673
+ console.debug("SessionMonitor._callback: Same sub still logged in at OP, session state has changed, restarting check session iframe; session_state:", sessionState);
674
+ }
675
+ }
676
+ else {
677
+ console.debug("SessionMonitor._callback: Different subject signed into OP:", iFrameIdTokenPayload.sub);
678
+ }
679
+ }).catch((e) => {
680
+ this.publishEvent(eventNames.logout_from_another_tab, {});
681
+ this.destroyAsync();
682
+ });
683
+ };
684
+
685
+ this.checkSessionIFrame = new CheckSessionIFrame(checkSessionCallback, clientId, checkSessionIFrameUri);
686
+ this.checkSessionIFrame.load().then(() => {
687
+ this.checkSessionIFrame.start(sessionState);
688
+ resolve();
689
+ }).catch((e) =>{
690
+ reject(e);
691
+ });
470
692
  } else {
471
- const session = initSession(this.configurationName);
472
- storage = new MemoryStorageBackend(session.saveItemsAsync, {});
693
+ resolve();
473
694
  }
474
-
475
- // @ts-ignore
476
- const queryStringUtil = configuration.redirect_uri.includes("#") ? new HashQueryStringUtils() : new NoHashQueryStringUtils();
477
- const authorizationHandler = new RedirectRequestHandler(storage, queryStringUtil, window.location, new DefaultCrypto());
478
- const authRequest = new AuthorizationRequest({
479
- client_id: configuration.client_id,
480
- redirect_uri: configuration.redirect_uri,
481
- scope: configuration.scope,
482
- response_type: AuthorizationRequest.RESPONSE_TYPE_CODE,
483
- state,
484
- extras: extras ?? configuration.extras
485
- });
486
- authorizationHandler.performAuthorizationRequest(oidcServerConfiguration, authRequest);
487
- } catch(exception) {
488
- this.publishEvent(eventNames.loginAsync_error, exception);
489
- throw exception;
490
- }
695
+ });
491
696
  }
492
697
 
493
- syncTokensAsyncPromise=null;
494
- async syncTokensAsync() {
495
- // Service Worker can be killed by the browser (when it wants,for example after 10 seconds of inactivity, so we retreieve the session if it happen)
496
- const configuration = this.configuration;
497
- if(!this.tokens){
498
- return;
698
+ loginCallbackPromise : Promise<any>=null
699
+ async loginCallbackAsync(isSilenSignin:boolean=false){
700
+ if(this.loginCallbackPromise !== null){
701
+ return this.loginCallbackPromise;
499
702
  }
500
-
501
- const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
502
- const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
503
- if (serviceWorker) {
504
- const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "syncTokensAsync");
505
- if(!tokens){
506
- try {
507
- this.publishEvent(eventNames.syncTokensAsync_begin, {});
508
- this.syncTokensAsyncPromise = this.silentSigninAsync();
509
- const silent_token_response = await this.syncTokensAsyncPromise;
510
- console.log("silent_token_response")
511
- console.log(silent_token_response)
512
- if (silent_token_response) {
513
- this.tokens = await setTokensAsync(serviceWorker, silent_token_response);
514
- } else{
515
- this.publishEvent(eventNames.syncTokensAsync_error, null);
516
- if(this.timeoutId){
517
- timer.clearTimeout(this.timeoutId);
518
- this.timeoutId=null;
519
- }
520
- return;
521
- }
522
- } catch (exceptionSilent) {
523
- console.error(exceptionSilent);
524
- this.publishEvent(eventNames.syncTokensAsync_error, exceptionSilent);
525
- if(this.timeoutId){
526
- timer.clearTimeout(this.timeoutId);
527
- this.timeoutId=null;
528
- }
529
- return;
530
- }
531
- this.syncTokensAsyncPromise = null;
532
- this.publishEvent(eventNames.syncTokensAsync_end, {});
703
+
704
+ const loginCallbackLocalAsync= async( ) =>{
705
+ const response = await this._loginCallbackAsync(isSilenSignin);
706
+ // @ts-ignore
707
+ const tokens = response.tokens;
708
+ const parsedTokens = await setTokensAsync(this.serviceWorker, tokens);
709
+ this.tokens = parsedTokens;
710
+ if(!this.serviceWorker){
711
+ await this.session.setTokens(parsedTokens);
533
712
  }
713
+ this.publishEvent(Oidc.eventNames.token_aquired, parsedTokens);
714
+ // @ts-ignore
715
+ return { parsedTokens, state:response.state, callbackPath : response.callbackPath};
534
716
  }
717
+
718
+ this.loginCallbackPromise = loginCallbackLocalAsync();
719
+ return this.loginCallbackPromise.then(result =>{
720
+ this.loginCallbackPromise = null;
721
+ return result;
722
+ })
535
723
  }
536
-
537
- async loginCallbackAsync(){
724
+
725
+ async _loginCallbackAsync(isSilentSignin:boolean=false){
538
726
  try {
539
727
  this.publishEvent(eventNames.loginCallbackAsync_begin, {});
540
728
  const configuration = this.configuration;
541
729
  const clientId = configuration.client_id;
542
- const redirectURL = configuration.redirect_uri;
730
+ const redirectUri = isSilentSignin ? configuration.silent_redirect_uri : configuration.redirect_uri;
543
731
  const authority = configuration.authority;
544
732
  const tokenRequestTimeout = configuration.token_request_timeout;
545
733
  const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
734
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
735
+ const sessionState = queryParams.session_state;
546
736
  const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
547
737
  let storage = null;
548
738
  if(serviceWorker){
@@ -556,16 +746,19 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
556
746
  throw new Error("Service Worker storage disapear");
557
747
  }
558
748
  await storage.removeItem("dummy");
749
+ await serviceWorker.setSessionStateAsync(sessionState);
559
750
  }else{
560
- const session = initSession(this.configurationName);
561
- this.session = session;
751
+
752
+ this.session = initSession(this.configurationName, redirectUri, configuration.storage ?? sessionStorage);
753
+ const session = initSession(this.configurationName, redirectUri);
754
+ session.setSessionState(sessionState);
562
755
  const items = await session.loadItemsAsync();
563
756
  storage = new MemoryStorageBackend(session.saveItemsAsync, items);
564
757
  }
565
758
  return new Promise((resolve, reject) => {
566
759
  // @ts-ignore
567
760
  let queryStringUtil = new NoHashQueryStringUtils();
568
- if(configuration.redirect_uri.includes("#")) {
761
+ if(redirectUri.includes("#")) {
569
762
  const splithash = window.location.href.split("#");
570
763
  if (splithash.length === 2 && splithash[1].includes("?")) {
571
764
  queryStringUtil = new HashQueryStringUtils();
@@ -599,14 +792,14 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
599
792
 
600
793
  const tokenRequest = new TokenRequest({
601
794
  client_id: clientId,
602
- redirect_uri: redirectURL,
795
+ redirect_uri: redirectUri,
603
796
  grant_type: GRANT_TYPE_AUTHORIZATION_CODE,
604
797
  code: response.code,
605
798
  refresh_token: undefined,
606
799
  extras,
607
800
  });
608
801
 
609
- let timeoutId = setTimeout(function(){
802
+ let timeoutId = setTimeout(()=>{
610
803
  reject("performTokenRequest timeout");
611
804
  timeoutId=null;
612
805
  }, tokenRequestTimeout ?? 12000);
@@ -614,14 +807,16 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
614
807
  const tokenHandler = new BaseTokenRequestHandler(new FetchRequestor());
615
808
  tokenHandler.performTokenRequest(oidcServerConfiguration, tokenRequest).then((tokenResponse)=>{
616
809
  if(timeoutId) {
617
- const loginParams = getLoginParams(this.configurationName);
618
810
  clearTimeout(timeoutId);
619
811
  this.timeoutId=null;
620
- this.publishEvent(eventNames.loginCallbackAsync_end, {});
621
- resolve({
622
- tokens: tokenResponse,
623
- state: request.state,
624
- callbackPath: loginParams.callbackPath,
812
+ const loginParams = getLoginParams(this.configurationName, redirectUri);
813
+ this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, clientId, sessionState, isSilentSignin).then(() =>{
814
+ this.publishEvent(eventNames.loginCallbackAsync_end, {});
815
+ resolve({
816
+ tokens: tokenResponse,
817
+ state: request.state,
818
+ callbackPath: loginParams.callbackPath,
819
+ });
625
820
  });
626
821
  }
627
822
  });
@@ -642,15 +837,14 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
642
837
  this.publishEvent(eventNames.loginCallbackAsync_error, exception);
643
838
  throw exception;
644
839
  }
645
-
646
840
  }
647
841
 
648
- async refreshTokensAsync(refreshToken, silentEvent = false) {
842
+ async refreshTokensAsync(refreshToken) {
649
843
  const localSilentSigninAsync= async (exception=null) => {
650
844
  try {
651
845
  const silent_token_response = await this.silentSigninAsync();
652
846
  if (silent_token_response) {
653
- return silent_token_response;
847
+ return silent_token_response.tokens;
654
848
  }
655
849
  } catch (exceptionSilent) {
656
850
  console.error(exceptionSilent);
@@ -659,12 +853,12 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
659
853
  timer.clearTimeout(this.timeoutId);
660
854
  this.timeoutId=null;
661
855
  }
662
- this.publishEvent(silentEvent ? eventNames.refreshTokensAsync_silent_error : eventNames.refreshTokensAsync_error, exception);
856
+ this.publishEvent(eventNames.refreshTokensAsync_error, exception);
663
857
  return null;
664
858
  }
665
859
 
666
860
  try{
667
- this.publishEvent(silentEvent ? eventNames.refreshTokensAsync_silent_begin : eventNames.refreshTokensAsync_begin, {})
861
+ this.publishEvent(eventNames.refreshTokensAsync_begin, {refreshToken:refreshToken})
668
862
  const configuration = this.configuration;
669
863
  const clientId = configuration.client_id;
670
864
  const redirectUri = configuration.redirect_uri;
@@ -696,14 +890,69 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
696
890
 
697
891
  const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
698
892
  const token_response = await tokenHandler.performTokenRequest(oidcServerConfiguration, request);
699
- this.publishEvent(silentEvent ? eventNames.refreshTokensAsync_silent_end :eventNames.refreshTokensAsync_end, {message:"success"});
893
+ this.publishEvent(eventNames.refreshTokensAsync_end, {message:"success"});
700
894
  return token_response;
701
895
  } catch(exception) {
702
896
  console.error(exception);
897
+ this.publishEvent(eventNames.refreshTokensAsync_silent_error, exception);
703
898
  return await localSilentSigninAsync(exception);
704
899
  }
705
900
  }
706
901
 
902
+ syncTokensAsyncPromise=null;
903
+ async syncTokensAsync() {
904
+ // Service Worker can be killed by the browser (when it wants,for example after 10 seconds of inactivity, so we retreieve the session if it happen)
905
+ const configuration = this.configuration;
906
+ if(!this.tokens){
907
+ return;
908
+ }
909
+
910
+ const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
911
+ const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
912
+ if (serviceWorker) {
913
+ const { isLogin } = await serviceWorker.initAsync(oidcServerConfiguration, "syncTokensAsync");
914
+ if(isLogin == false){
915
+ this.publishEvent(eventNames.logout_from_another_tab, {});
916
+ await this.destroyAsync();
917
+ }
918
+ else if (isLogin == null){
919
+ try {
920
+ this.publishEvent(eventNames.syncTokensAsync_begin, {});
921
+ this.syncTokensAsyncPromise = this.silentSigninAsync({prompt:"none"});
922
+ const silent_token_response = await this.syncTokensAsyncPromise;
923
+ if (silent_token_response && silent_token_response.tokens) {
924
+ this.tokens = await setTokensAsync(serviceWorker, silent_token_response.tokens);
925
+ } else{
926
+ this.publishEvent(eventNames.syncTokensAsync_error, null);
927
+ if(this.timeoutId){
928
+ timer.clearTimeout(this.timeoutId);
929
+ this.timeoutId=null;
930
+ }
931
+ return;
932
+ }
933
+ } catch (exceptionSilent) {
934
+ console.error(exceptionSilent);
935
+ this.publishEvent(eventNames.syncTokensAsync_error, exceptionSilent);
936
+ if(this.timeoutId){
937
+ timer.clearTimeout(this.timeoutId);
938
+ this.timeoutId=null;
939
+ }
940
+ return;
941
+ }
942
+ this.syncTokensAsyncPromise = null;
943
+ this.publishEvent(eventNames.syncTokensAsync_end, {});
944
+ }
945
+ } else {
946
+ const session = initSession(this.configurationName, configuration.redirect_uri, configuration.storage ?? sessionStorage);
947
+ const {tokens} = await session.initAsync();
948
+ if(!tokens){
949
+ this.publishEvent(eventNames.logout_from_another_tab, {});
950
+ await this.destroyAsync();
951
+ }
952
+ }
953
+ }
954
+
955
+
707
956
  loginCallbackWithAutoTokensRenewPromise:Promise<loginCallbackResult> = null;
708
957
  loginCallbackWithAutoTokensRenewAsync():Promise<loginCallbackResult>{
709
958
  if(this.loginCallbackWithAutoTokensRenewPromise !== null){
@@ -721,6 +970,11 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
721
970
  }
722
971
 
723
972
  async destroyAsync() {
973
+ timer.clearTimeout(this.timeoutId);
974
+ this.timeoutId=null;
975
+ if(this.checkSessionIFrame){
976
+ this.checkSessionIFrame.stop();
977
+ }
724
978
  if(this.serviceWorker){
725
979
  await this.serviceWorker.clearAsync();
726
980
  }
@@ -730,8 +984,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
730
984
  this.tokens = null;
731
985
  this.userInfo = null;
732
986
  this.events = [];
733
- timer.clearTimeout(this.timeoutId);
734
- this.timeoutId=null;
987
+
735
988
  }
736
989
 
737
990
  async logoutAsync(callbackPathOrUrl: string | undefined = undefined, extras: StringMap = null) {