@axa-fr/oidc-client 7.4.0 → 7.5.0

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/src/jwt.ts ADDED
@@ -0,0 +1,248 @@
1
+ // code base on https://coolaj86.com/articles/sign-jwt-webcrypto-vanilla-js/
2
+
3
+ // String (UCS-2) to Uint8Array
4
+ //
5
+ // because... JavaScript, Strings, and Buffers
6
+ // @ts-ignore
7
+ function strToUint8(str) {
8
+ return new TextEncoder().encode(str);
9
+ }
10
+
11
+ // Binary String to URL-Safe Base64
12
+ //
13
+ // btoa (Binary-to-Ascii) means "binary string" to base64
14
+ // @ts-ignore
15
+ function binToUrlBase64(bin) {
16
+ return btoa(bin)
17
+ .replace(/\+/g, '-')
18
+ .replace(/\//g, '_')
19
+ .replace(/=+/g, '');
20
+ }
21
+
22
+ // UTF-8 to Binary String
23
+ //
24
+ // Because JavaScript has a strange relationship with strings
25
+ // https://coolaj86.com/articles/base64-unicode-utf-8-javascript-and-you/
26
+ // @ts-ignore
27
+ function utf8ToBinaryString(str) {
28
+ const escstr = encodeURIComponent(str);
29
+ // replaces any uri escape sequence, such as %0A,
30
+ // with binary escape, such as 0x0A
31
+ const binstr = escstr.replace(/%([0-9A-F]{2})/g, function (match, p1) {
32
+ return String.fromCharCode(parseInt(p1, 16));
33
+ });
34
+
35
+ return binstr;
36
+ }
37
+
38
+ // Uint8Array to URL Safe Base64
39
+ //
40
+ // the shortest distant between two encodings... binary string
41
+ // @ts-ignore
42
+ function uint8ToUrlBase64(uint8) {
43
+ let bin = '';
44
+ // @ts-ignore
45
+ uint8.forEach(function(code) {
46
+ bin += String.fromCharCode(code);
47
+ });
48
+ return binToUrlBase64(bin);
49
+ }
50
+
51
+ // UCS-2 String to URL-Safe Base64
52
+ //
53
+ // btoa doesn't work on UTF-8 strings
54
+ // @ts-ignore
55
+ function strToUrlBase64(str) {
56
+ return binToUrlBase64(utf8ToBinaryString(str));
57
+ }
58
+
59
+ export var JWT = {};
60
+ // @ts-ignore
61
+ JWT.sign = (jwk, headers, claims, jwtHeaderType= 'dpop+jwt') => {
62
+ // Make a shallow copy of the key
63
+ // (to set ext if it wasn't already set)
64
+ jwk = Object.assign({}, jwk);
65
+
66
+ // The headers should probably be empty
67
+ headers.typ = jwtHeaderType;
68
+ headers.alg = 'ES256';
69
+ if (!headers.kid) {
70
+ // alternate: see thumbprint function below
71
+ headers.jwk = { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y };
72
+ }
73
+
74
+ const jws = {
75
+ // @ts-ignore
76
+ // JWT "headers" really means JWS "protected headers"
77
+ protected: strToUrlBase64(JSON.stringify(headers)),
78
+ // @ts-ignore
79
+ // JWT "claims" are really a JSON-defined JWS "payload"
80
+ payload: strToUrlBase64(JSON.stringify(claims))
81
+ };
82
+
83
+ // To import as EC (ECDSA, P-256, SHA-256, ES256)
84
+ const keyType = {
85
+ name: 'ECDSA',
86
+ namedCurve: 'P-256',
87
+ hash: {name: 'ES256'}
88
+ };
89
+
90
+ // To make re-exportable as JSON (or DER/PEM)
91
+ const exportable = true;
92
+
93
+ // Import as a private key that isn't black-listed from signing
94
+ const privileges = ['sign'];
95
+
96
+ // Actually do the import, which comes out as an abstract key type
97
+ // @ts-ignore
98
+ return window.crypto.subtle
99
+ // @ts-ignore
100
+ .importKey('jwk', jwk, keyType, exportable, privileges)
101
+ .then(function(privateKey) {
102
+ // Convert UTF-8 to Uint8Array ArrayBuffer
103
+ // @ts-ignore
104
+ const data = strToUint8(jws.protected + '.' + jws.payload);
105
+
106
+ // The signature and hash should match the bit-entropy of the key
107
+ // https://tools.ietf.org/html/rfc7518#section-3
108
+ const signatureType = {name: 'ECDSA', hash: {name: 'SHA-256'}};
109
+
110
+ return window.crypto.subtle.sign(signatureType, privateKey, data).then(function(signature) {
111
+ // returns an ArrayBuffer containing a JOSE (not X509) signature,
112
+ // which must be converted to Uint8 to be useful
113
+ // @ts-ignore
114
+ jws.signature = uint8ToUrlBase64(new Uint8Array(signature));
115
+
116
+ // JWT is just a "compressed", "protected" JWS
117
+ // @ts-ignore
118
+ return jws.protected + '.' + jws.payload + '.' + jws.signature;
119
+ });
120
+ });
121
+ };
122
+
123
+
124
+ const EC = {};
125
+ // @ts-ignore
126
+ EC.generate = function() {
127
+ const keyType = {
128
+ name: 'ECDSA',
129
+ namedCurve: 'P-256'
130
+ };
131
+ const exportable = true;
132
+ const privileges = ['sign', 'verify'];
133
+ // @ts-ignore
134
+ return window.crypto.subtle.generateKey(keyType, exportable, privileges).then(function(key) {
135
+ // returns an abstract and opaque WebCrypto object,
136
+ // which in most cases you'll want to export as JSON to be able to save
137
+ return window.crypto.subtle.exportKey('jwk', key.privateKey);
138
+ });
139
+ };
140
+
141
+ // Create a Public Key from a Private Key
142
+ //
143
+ // chops off the private parts
144
+ // @ts-ignore
145
+ EC.neuter = function(jwk) {
146
+ const copy = Object.assign({}, jwk);
147
+ delete copy.d;
148
+ copy.key_ops = ['verify'];
149
+ return copy;
150
+ };
151
+
152
+ export var JWK = {};
153
+ // @ts-ignore
154
+ JWK.thumbprint = function(jwk) {
155
+ // lexigraphically sorted, no spaces
156
+ const sortedPub = '{"crv":"CRV","kty":"EC","x":"X","y":"Y"}'
157
+ .replace('CRV', jwk.crv)
158
+ .replace('X', jwk.x)
159
+ .replace('Y', jwk.y);
160
+
161
+ // The hash should match the size of the key,
162
+ // but we're only dealing with P-256
163
+ return window.crypto.subtle
164
+ .digest({ name: 'SHA-256' }, strToUint8(sortedPub))
165
+ .then(function(hash) {
166
+ return uint8ToUrlBase64(new Uint8Array(hash));
167
+ });
168
+ };
169
+
170
+
171
+ const guid = function () {
172
+ // RFC4122: The version 4 UUID is meant for generating UUIDs from truly-random or
173
+ // pseudo-random numbers.
174
+ // The algorithm is as follows:
175
+ // Set the two most significant bits (bits 6 and 7) of the
176
+ // clock_seq_hi_and_reserved to zero and one, respectively.
177
+ // Set the four most significant bits (bits 12 through 15) of the
178
+ // time_hi_and_version field to the 4-bit version number from
179
+ // Section 4.1.3. Version4
180
+ // Set all the other bits to randomly (or pseudo-randomly) chosen
181
+ // values.
182
+ // UUID = time-low "-" time-mid "-"time-high-and-version "-"clock-seq-reserved and low(2hexOctet)"-" node
183
+ // time-low = 4hexOctet
184
+ // time-mid = 2hexOctet
185
+ // time-high-and-version = 2hexOctet
186
+ // clock-seq-and-reserved = hexOctet:
187
+ // clock-seq-low = hexOctet
188
+ // node = 6hexOctet
189
+ // Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
190
+ // y could be 1000, 1001, 1010, 1011 since most significant two bits needs to be 10
191
+ // y values are 8, 9, A, B
192
+ const guidHolder = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
193
+ const hex = '0123456789abcdef';
194
+ let r = 0;
195
+ let guidResponse = "";
196
+ for (let i = 0; i < 36; i++) {
197
+ if (guidHolder[i] !== '-' && guidHolder[i] !== '4') {
198
+ // each x and y needs to be random
199
+ r = Math.random() * 16 | 0;
200
+ }
201
+
202
+ if (guidHolder[i] === 'x') {
203
+ guidResponse += hex[r];
204
+ } else if (guidHolder[i] === 'y') {
205
+ // clock-seq-and-reserved first hex is filtered and remaining hex values are random
206
+ r &= 0x3; // bit and with 0011 to set pos 2 to zero ?0??
207
+ r |= 0x8; // set pos 3 to 1 as 1???
208
+ guidResponse += hex[r];
209
+ } else {
210
+ guidResponse += guidHolder[i];
211
+ }
212
+ }
213
+
214
+ return guidResponse;
215
+ };
216
+
217
+
218
+ export const generateJwkAsync = () => {
219
+ // @ts-ignore
220
+ return EC.generate().then(function(jwk) {
221
+ // console.info('Private Key:', JSON.stringify(jwk));
222
+ // @ts-ignore
223
+ // console.info('Public Key:', JSON.stringify(EC.neuter(jwk)));
224
+ return jwk;
225
+ });
226
+ }
227
+
228
+ export const generateJwtDemonstratingProofOfPossessionAsync = (jwk, method = 'POST', url: string, extrasClaims={}) => {
229
+
230
+ const claims = {
231
+ // https://www.rfc-editor.org/rfc/rfc9449.html#name-concept
232
+ jit: btoa(guid()),
233
+ htm: method,
234
+ htu: url,
235
+ iat: Math.round(Date.now() / 1000),
236
+ ...extrasClaims,
237
+ };
238
+ // @ts-ignore
239
+ return JWK.thumbprint(jwk).then(function(kid) {
240
+ // @ts-ignore
241
+ return JWT.sign(jwk, { /*kid: kid*/ }, claims).then(function(jwt) {
242
+ // console.info('JWT:', jwt);
243
+ return jwt;
244
+ });
245
+ });
246
+ }
247
+
248
+ export default EC;
package/src/login.ts CHANGED
@@ -1,14 +1,18 @@
1
- import { generateRandom } from './crypto.js';
2
- import { eventNames } from './events.js';
3
- import { initSession } from './initSession.js';
4
- import { initWorkerAsync } from './initWorker.js';
5
- import { isTokensOidcValid } from './parseTokens.js';
6
- import { performAuthorizationRequestAsync, performFirstTokenRequestAsync } from './requests.js';
7
- import { getParseQueryStringFromLocation } from './route-utils.js';
8
- import { OidcConfiguration, StringMap } from './types.js';
1
+ import {generateRandom} from './crypto.js';
2
+ import {eventNames} from './events.js';
3
+ import {initSession} from './initSession.js';
4
+ import {initWorkerAsync} from './initWorker.js';
5
+ import {isTokensOidcValid} from './parseTokens.js';
6
+ import {
7
+ performAuthorizationRequestAsync,
8
+ performFirstTokenRequestAsync
9
+ } from './requests.js';
10
+ import {getParseQueryStringFromLocation} from './route-utils.js';
11
+ import {OidcConfiguration, StringMap} from './types.js';
12
+ import {generateJwkAsync, generateJwtDemonstratingProofOfPossessionAsync} from "./jwt";
9
13
 
10
14
  // eslint-disable-next-line @typescript-eslint/ban-types
11
- export const defaultLoginAsync = (window, configurationName, configuration:OidcConfiguration, publishEvent :(string, any)=>void, initAsync:Function) => (callbackPath:string = undefined, extras:StringMap = null, isSilentSignin = false, scope:string = undefined) => {
15
+ export const defaultLoginAsync = (window, configurationName:string, configuration:OidcConfiguration, publishEvent :(string, any)=>void, initAsync:Function) => (callbackPath:string = undefined, extras:StringMap = null, isSilentSignin = false, scope:string = undefined) => {
12
16
  const originExtras = extras;
13
17
  extras = { ...extras };
14
18
  const loginLocalAsync = async () => {
@@ -42,14 +46,14 @@ export const defaultLoginAsync = (window, configurationName, configuration:OidcC
42
46
  const oidcServerConfiguration = await initAsync(configuration.authority, configuration.authority_configuration);
43
47
  let storage;
44
48
  if (serviceWorker) {
45
- serviceWorker.setLoginParams(configurationName, { callbackPath: url, extras: originExtras });
49
+ serviceWorker.setLoginParams({ callbackPath: url, extras: originExtras });
46
50
  await serviceWorker.initAsync(oidcServerConfiguration, 'loginAsync', configuration);
47
51
  await serviceWorker.setNonceAsync(nonce);
48
52
  serviceWorker.startKeepAliveServiceWorker();
49
53
  storage = serviceWorker;
50
54
  } else {
51
55
  const session = initSession(configurationName, configuration.storage ?? sessionStorage);
52
- session.setLoginParams(configurationName, { callbackPath: url, extras: originExtras });
56
+ session.setLoginParams({ callbackPath: url, extras: originExtras });
53
57
  await session.setNonceAsync(nonce);
54
58
  storage = session;
55
59
  }
@@ -91,7 +95,7 @@ export const loginCallbackAsync = (oidc) => async (isSilentSignin = false) => {
91
95
  await serviceWorker.initAsync(oidcServerConfiguration, 'loginCallbackAsync', configuration);
92
96
  await serviceWorker.setSessionStateAsync(sessionState);
93
97
  nonceData = await serviceWorker.getNonceAsync();
94
- getLoginParams = serviceWorker.getLoginParams(oidc.configurationName);
98
+ getLoginParams = serviceWorker.getLoginParams();
95
99
  state = await serviceWorker.getStateAsync();
96
100
  serviceWorker.startKeepAliveServiceWorker();
97
101
  storage = serviceWorker;
@@ -99,7 +103,7 @@ export const loginCallbackAsync = (oidc) => async (isSilentSignin = false) => {
99
103
  const session = initSession(oidc.configurationName, configuration.storage ?? sessionStorage);
100
104
  await session.setSessionStateAsync(sessionState);
101
105
  nonceData = await session.getNonceAsync();
102
- getLoginParams = session.getLoginParams(oidc.configurationName);
106
+ getLoginParams = session.getLoginParams();
103
107
  state = await session.getStateAsync();
104
108
  storage = session;
105
109
  }
@@ -135,8 +139,25 @@ export const loginCallbackAsync = (oidc) => async (isSilentSignin = false) => {
135
139
  }
136
140
  }
137
141
  }
142
+
143
+ const url = oidcServerConfiguration.tokenEndpoint;
144
+ const headersExtras = {};
145
+ if(configuration.demonstrating_proof_of_possession) {
146
+ const jwk = await generateJwkAsync();
147
+ if (serviceWorker) {
148
+ await serviceWorker.setDemonstratingProofOfPossessionJwkAsync(jwk);
149
+ } else {
150
+ const session = initSession(oidc.configurationName, configuration.storage);
151
+ await session.setDemonstratingProofOfPossessionJwkAsync(jwk);
152
+ }
153
+ headersExtras['DPoP'] = await generateJwtDemonstratingProofOfPossessionAsync(jwk, 'POST', url);
154
+ }
138
155
 
139
- const tokenResponse = await performFirstTokenRequestAsync(storage)(oidcServerConfiguration.tokenEndpoint, { ...data, ...extras }, oidc.configuration.token_renew_mode, tokenRequestTimeout);
156
+ const tokenResponse = await performFirstTokenRequestAsync(storage)(url,
157
+ { ...data, ...extras },
158
+ headersExtras,
159
+ oidc.configuration.token_renew_mode,
160
+ tokenRequestTimeout);
140
161
 
141
162
  if (!tokenResponse.success) {
142
163
  throw new Error('Token request failed');
@@ -144,13 +165,8 @@ export const loginCallbackAsync = (oidc) => async (isSilentSignin = false) => {
144
165
 
145
166
  let loginParams;
146
167
  const formattedTokens = tokenResponse.data.tokens;
147
- if (serviceWorker) {
148
- await serviceWorker.initAsync(redirectUri, 'syncTokensAsync', configuration);
149
- loginParams = serviceWorker.getLoginParams(oidc.configurationName);
150
- } else {
151
- const session = initSession(oidc.configurationName, configuration.storage);
152
- loginParams = session.getLoginParams(oidc.configurationName);
153
- }
168
+ const demonstratingProofOfPossessionNonce = tokenResponse.data.demonstratingProofOfPossessionNonce;
169
+
154
170
  // @ts-ignore
155
171
  if (tokenResponse.data.state !== extras.state) {
156
172
  throw new Error('state is not valid');
@@ -159,6 +175,30 @@ export const loginCallbackAsync = (oidc) => async (isSilentSignin = false) => {
159
175
  if (!isValid) {
160
176
  throw new Error(`Tokens are not OpenID valid, reason: ${reason}`);
161
177
  }
178
+
179
+ if(serviceWorker){
180
+ if(formattedTokens.refreshToken && !formattedTokens.refreshToken.includes("SECURED_BY_OIDC_SERVICE_WORKER")) {
181
+ throw new Error("Refresh token should be hidden by service worker");
182
+ }
183
+
184
+ if(demonstratingProofOfPossessionNonce && formattedTokens.accessToken && formattedTokens.accessToken.includes("SECURED_BY_OIDC_SERVICE_WORKER")) {
185
+ throw new Error("Demonstration of proof of possession require Access token not hidden by service worker");
186
+ }
187
+ }
188
+
189
+ if (serviceWorker) {
190
+ await serviceWorker.initAsync(redirectUri, 'syncTokensAsync', configuration);
191
+ loginParams = serviceWorker.getLoginParams();
192
+ if(demonstratingProofOfPossessionNonce) {
193
+ await serviceWorker.setDemonstratingProofOfPossessionNonce(demonstratingProofOfPossessionNonce);
194
+ }
195
+ } else {
196
+ const session = initSession(oidc.configurationName, configuration.storage);
197
+ loginParams = session.getLoginParams();
198
+ if(demonstratingProofOfPossessionNonce) {
199
+ await session.setDemonstratingProofOfPossessionNonce(demonstratingProofOfPossessionNonce);
200
+ }
201
+ }
162
202
 
163
203
  await oidc.startCheckSessionAsync(oidcServerConfiguration.checkSessionIframe, clientId, sessionState, isSilentSignin);
164
204
  oidc.publishEvent(eventNames.loginCallbackAsync_end, {});
package/src/oidc.ts CHANGED
@@ -1,23 +1,20 @@
1
- import { startCheckSessionAsync as defaultStartCheckSessionAsync } from './checkSession.js';
2
- import { CheckSessionIFrame } from './checkSessionIFrame.js';
3
- import { eventNames } from './events.js';
4
- import { initSession } from './initSession.js';
5
- import { initWorkerAsync, sleepAsync } from './initWorker.js';
6
- import { defaultLoginAsync, loginCallbackAsync } from './login.js';
7
- import { destroyAsync, logoutAsync } from './logout.js';
8
- import {
9
- computeTimeLeft,
10
- isTokensOidcValid,
11
- setTokens, TokenRenewMode,
12
- Tokens,
13
- } from './parseTokens.js';
14
- import { autoRenewTokens, renewTokensAndStartTimerAsync } from './renewTokens.js';
15
- import { fetchFromIssuer, performTokenRequestAsync } from './requests.js';
16
- import { getParseQueryStringFromLocation } from './route-utils.js';
17
- import defaultSilentLoginAsync, { _silentLoginAsync } from './silentLogin.js';
1
+ import {startCheckSessionAsync as defaultStartCheckSessionAsync} from './checkSession.js';
2
+ import {CheckSessionIFrame} from './checkSessionIFrame.js';
3
+ import {eventNames} from './events.js';
4
+ import {initSession} from './initSession.js';
5
+ import {initWorkerAsync, sleepAsync} from './initWorker.js';
6
+ import {defaultLoginAsync, loginCallbackAsync} from './login.js';
7
+ import {destroyAsync, logoutAsync} from './logout.js';
8
+ import {computeTimeLeft, isTokensOidcValid, setTokens, TokenRenewMode, Tokens,} from './parseTokens.js';
9
+ import {autoRenewTokens, renewTokensAndStartTimerAsync} from './renewTokens.js';
10
+ import {fetchFromIssuer, performTokenRequestAsync} from './requests.js';
11
+ import {getParseQueryStringFromLocation} from './route-utils.js';
12
+ import defaultSilentLoginAsync, {_silentLoginAsync} from './silentLogin.js';
18
13
  import timer from './timer.js';
19
- import { AuthorityConfiguration, Fetch, OidcConfiguration, StringMap } from './types.js';
20
- import { userInfoAsync } from './user.js';
14
+ import {AuthorityConfiguration, Fetch, OidcConfiguration, StringMap} from './types.js';
15
+ import {userInfoAsync} from './user.js';
16
+ import {base64urlOfHashOfASCIIEncodingAsync} from "./crypto";
17
+ import {generateJwtDemonstratingProofOfPossessionAsync} from "./jwt";
21
18
 
22
19
  export const getFetchDefault = () => {
23
20
  return fetch;
@@ -93,12 +90,6 @@ export class Oidc {
93
90
  if (refresh_time_before_tokens_expiration_in_second > 60) {
94
91
  refresh_time_before_tokens_expiration_in_second = refresh_time_before_tokens_expiration_in_second - Math.floor(Math.random() * 40);
95
92
  }
96
- if (!configuration.logout_tokens_to_invalidate) {
97
- configuration.logout_tokens_to_invalidate = ['access_token', 'refresh_token'];
98
- }
99
- if (!configuration.authority_timeout_wellknowurl_in_millisecond) {
100
- configuration.authority_timeout_wellknowurl_in_millisecond = 10000;
101
- }
102
93
  this.configuration = {
103
94
  ...configuration,
104
95
  silent_login_uri,
@@ -106,6 +97,9 @@ export class Oidc {
106
97
  refresh_time_before_tokens_expiration_in_second,
107
98
  silent_login_timeout: configuration.silent_login_timeout ?? 12000,
108
99
  token_renew_mode: configuration.token_renew_mode ?? TokenRenewMode.access_token_or_id_token_invalid,
100
+ demonstrating_proof_of_possession: configuration.demonstrating_proof_of_possession ?? false,
101
+ authority_timeout_wellknowurl_in_millisecond: configuration.authority_timeout_wellknowurl_in_millisecond ?? 10000,
102
+ logout_tokens_to_invalidate: configuration.logout_tokens_to_invalidate ?? ['access_token', 'refresh_token'],
109
103
  };
110
104
  this.getFetch = getFetch ?? getFetchDefault;
111
105
  this.configurationName = configurationName;
@@ -259,7 +253,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
259
253
  if (tokens) {
260
254
  // @ts-ignore
261
255
  this.tokens = setTokens(tokens, null, configuration.token_renew_mode);
262
- const getLoginParams = session.getLoginParams(this.configurationName);
256
+ const getLoginParams = session.getLoginParams();
263
257
  // @ts-ignore
264
258
  this.timeoutId = autoRenewTokens(this, tokens.refreshToken, this.tokens.expiresAt, getLoginParams.extras);
265
259
  const sessionState = await session.getSessionStateAsync();
@@ -329,11 +323,6 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
329
323
  if (!serviceWorker) {
330
324
  const session = initSession(this.configurationName, this.configuration.storage);
331
325
  session.setTokens(parsedTokens);
332
- } else {
333
- // @ts-ignore
334
- if(!parsedTokens.fromServiceWorker){
335
- throw Error('security issue, parsedTokens.fromServiceWorker is not defined');
336
- }
337
326
  }
338
327
  this.publishEvent(Oidc.eventNames.token_aquired, parsedTokens);
339
328
  // @ts-ignore
@@ -378,10 +367,10 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
378
367
  let loginParams;
379
368
  const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
380
369
  if (serviceWorker) {
381
- loginParams = serviceWorker.getLoginParams(this.configurationName);
370
+ loginParams = serviceWorker.getLoginParams();
382
371
  } else {
383
372
  const session = initSession(this.configurationName, configuration.storage);
384
- loginParams = session.getLoginParams(this.configurationName);
373
+ loginParams = session.getLoginParams();
385
374
  }
386
375
  const silent_token_response = await silentLoginAsync({
387
376
  ...loginParams.extras,
@@ -461,7 +450,19 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
461
450
  };
462
451
  const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
463
452
  const timeoutMs = document.hidden ? 10000 : 30000 * 10;
464
- const tokenResponse = await performTokenRequestAsync(this.getFetch())(oidcServerConfiguration.tokenEndpoint, details, finalExtras, tokens, configuration.token_renew_mode, timeoutMs);
453
+ const url = oidcServerConfiguration.tokenEndpoint;
454
+ const headersExtras = {};
455
+ if(configuration.demonstrating_proof_of_possession) {
456
+ headersExtras['DPoP'] = await this.generateDemonstrationOfProofOfPossessionAsync(tokens.accessToken, url, 'POST');
457
+ }
458
+ const tokenResponse = await performTokenRequestAsync(this.getFetch())(url,
459
+ details,
460
+ finalExtras,
461
+ tokens,
462
+ headersExtras,
463
+ configuration.token_renew_mode,
464
+ timeoutMs);
465
+
465
466
  if (tokenResponse.success) {
466
467
  const { isValid, reason } = isTokensOidcValid(tokenResponse.data, nonce.nonce, oidcServerConfiguration);
467
468
  if (!isValid) {
@@ -470,6 +471,15 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
470
471
  return { tokens: null, status: 'SESSION_LOST' };
471
472
  }
472
473
  updateTokens(tokenResponse.data);
474
+ if(tokenResponse.demonstratingProofOfPossessionNonce) {
475
+ const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
476
+ if(serviceWorker){
477
+ await serviceWorker.setDemonstratingProofOfPossessionNonce(tokenResponse.demonstratingProofOfPossessionNonce);
478
+ } else {
479
+ const session = initSession(this.configurationName, configuration.storage);
480
+ await session.setDemonstratingProofOfPossessionNonce(tokenResponse.demonstratingProofOfPossessionNonce);
481
+ }
482
+ }
473
483
  this.publishEvent(eventNames.refreshTokensAsync_end, { success: tokenResponse.success });
474
484
  this.publishEvent(Oidc.eventNames.token_renewed, { reason: 'REFRESH_TOKEN' });
475
485
  return { tokens: tokenResponse.data, status: 'LOGGED_IN' };
@@ -491,6 +501,30 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
491
501
  }
492
502
  }
493
503
 
504
+ async generateDemonstrationOfProofOfPossessionAsync(accessToken:string, url:string, method:string): Promise<string> {
505
+
506
+ const configuration = this.configuration;
507
+ const claimsExtras = {ath: await base64urlOfHashOfASCIIEncodingAsync(accessToken),};
508
+
509
+ const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
510
+ let demonstratingProofOfPossessionNonce:string = null;
511
+ let jwk;
512
+ if (serviceWorker) {
513
+ demonstratingProofOfPossessionNonce = await serviceWorker.getDemonstratingProofOfPossessionNonce();
514
+ jwk = await serviceWorker.getDemonstratingProofOfPossessionJwkAsync();
515
+ } else {
516
+ const session = initSession(this.configurationName, configuration.storage);
517
+ jwk = await session.getDemonstratingProofOfPossessionJwkAsync();
518
+ demonstratingProofOfPossessionNonce = await session.getDemonstratingProofOfPossessionNonce();
519
+ }
520
+
521
+ if (demonstratingProofOfPossessionNonce) {
522
+ claimsExtras['nonce'] = demonstratingProofOfPossessionNonce;
523
+ }
524
+
525
+ return await generateJwtDemonstratingProofOfPossessionAsync(jwk, method, url, claimsExtras);
526
+ }
527
+
494
528
  async syncTokensInfoAsync(configuration, configurationName, currentTokens, forceRefresh = false) {
495
529
  // 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
530
  // const configuration = this.configuration;
package/src/oidcClient.ts CHANGED
@@ -65,6 +65,10 @@ export class OidcClient {
65
65
  return this._oidc.configuration;
66
66
  }
67
67
 
68
+ async generateDemonstrationOfProofOfPossessionAsync(accessToken:string, url:string, method:string) : Promise<string> {
69
+ return this._oidc.generateDemonstrationOfProofOfPossessionAsync(accessToken, url, method);
70
+ }
71
+
68
72
  async getValidTokenAsync(waitMs = 200, numberWait = 50): Promise<ValidToken> {
69
73
  return getValidTokenAsync(this._oidc, waitMs, numberWait);
70
74
  }
@@ -1,4 +1,4 @@
1
- import { sleepAsync } from './initWorker.js';
1
+ import {sleepAsync} from './initWorker.js';
2
2
 
3
3
  const b64DecodeUnicode = (str) =>
4
4
  decodeURIComponent(Array.prototype.map.call(atob(str), (c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''));
@@ -46,20 +46,29 @@ export const TokenRenewMode = {
46
46
  id_token_invalid: 'id_token_invalid',
47
47
  };
48
48
 
49
+ function extractedIssueAt(tokens, accessTokenPayload, _idTokenPayload) {
50
+ if (!tokens.issuedAt) {
51
+ if (accessTokenPayload && accessTokenPayload.iat) {
52
+ return accessTokenPayload.iat;
53
+ } else if (_idTokenPayload && _idTokenPayload.iat) {
54
+ return _idTokenPayload.iat;
55
+ } else {
56
+ const currentTimeUnixSecond = new Date().getTime() / 1000;
57
+ return currentTimeUnixSecond;
58
+ }
59
+ } else if (typeof tokens.issuedAt == "string") {
60
+ return parseInt(tokens.issuedAt, 10);
61
+ }
62
+ return tokens.issuedAt;
63
+ }
64
+
49
65
  export const setTokens = (tokens, oldTokens = null, tokenRenewMode: string):Tokens => {
50
66
  if (!tokens) {
51
67
  return null;
52
68
  }
53
69
  let accessTokenPayload;
54
70
  const expireIn = typeof tokens.expiresIn == "string" ? parseInt(tokens.expiresIn, 10) : tokens.expiresIn;
55
-
56
- if (!tokens.issuedAt) {
57
- const currentTimeUnixSecond = new Date().getTime() / 1000;
58
- tokens.issuedAt = currentTimeUnixSecond;
59
- } else if (typeof tokens.issuedAt == "string") {
60
- tokens.issuedAt = parseInt(tokens.issuedAt, 10);
61
- }
62
-
71
+
63
72
  if (tokens.accessTokenPayload !== undefined) {
64
73
  accessTokenPayload = tokens.accessTokenPayload;
65
74
  } else {
@@ -70,6 +79,8 @@ export const setTokens = (tokens, oldTokens = null, tokenRenewMode: string):Toke
70
79
  const idTokenExpireAt = (_idTokenPayload && _idTokenPayload.exp) ? _idTokenPayload.exp : Number.MAX_VALUE;
71
80
  const accessTokenExpiresAt = (accessTokenPayload && accessTokenPayload.exp) ? accessTokenPayload.exp : tokens.issuedAt + expireIn;
72
81
 
82
+ tokens.issuedAt = extractedIssueAt(tokens, accessTokenPayload, _idTokenPayload);
83
+
73
84
  let expiresAt;
74
85
  if(tokens.expiresAt)
75
86
  {
@@ -127,18 +138,16 @@ export const parseOriginalTokens = (tokens, oldTokens, tokenRenewMode: string) =
127
138
  // @ts-ignore
128
139
  data.idTokenPayload = tokens.idTokenPayload;
129
140
  }
130
-
131
- if(tokens.fromServiceWorker !== undefined) {
132
- // @ts-ignore
133
- data.fromServiceWorker = tokens.fromServiceWorker;
134
- }
135
141
 
136
142
  return setTokens(data, oldTokens, tokenRenewMode);
137
143
  };
138
144
 
139
145
  export const computeTimeLeft = (refreshTimeBeforeTokensExpirationInSecond, expiresAt) => {
140
146
  const currentTimeUnixSecond = new Date().getTime() / 1000;
141
- return Math.round(((expiresAt - refreshTimeBeforeTokensExpirationInSecond) - currentTimeUnixSecond));
147
+
148
+ const timeLeftSecond = expiresAt - currentTimeUnixSecond;
149
+
150
+ return Math.round(timeLeftSecond - refreshTimeBeforeTokensExpirationInSecond);
142
151
  };
143
152
 
144
153
  export const isTokensValid = (tokens) => {