-
-
Access Token
-
Please consider to configure the ServiceWorker in order to protect your application from XSRF attacks. "access_token" and "refresh_token" will never be accessible from your client side javascript.
- {
Access Token: {JSON.stringify(accessToken)}
}
- {accessTokenPayload != null &&
Access Token Payload: {JSON.stringify(accessTokenPayload)}
}
-
Id Token
- {idTokenPayload != null &&
Access Token Payload: {JSON.stringify(idTokenPayload)}
}
+ <>
+
+
+
Access Token
+
Please consider to configure the ServiceWorker in order to protect your application from XSRF attacks. "access_token" and "refresh_token" will never be accessible from your client side javascript.
+ {
Access Token: {JSON.stringify(accessToken)}
}
+ {accessTokenPayload != null &&
Access Token Payload: {JSON.stringify(accessTokenPayload)}
}
+
Id Token
+ {idTokenPayload != null &&
Access Token Payload: {JSON.stringify(idTokenPayload)}
}
+
-
+
+ >
);
};
diff --git a/packages/oidc-client-service-worker/package.json b/packages/oidc-client-service-worker/package.json
index 061e2a422..5aa9cf552 100644
--- a/packages/oidc-client-service-worker/package.json
+++ b/packages/oidc-client-service-worker/package.json
@@ -1,6 +1,6 @@
{
"name": "@axa-fr/oidc-client-service-worker",
- "version": "7.13.15",
+ "version": "7.13.16",
"type": "module",
"private": false,
"main": "dist/OidcServiceWorker.js",
diff --git a/packages/oidc-client-service-worker/src/version.ts b/packages/oidc-client-service-worker/src/version.ts
index 8e8511c92..52e8c9412 100644
--- a/packages/oidc-client-service-worker/src/version.ts
+++ b/packages/oidc-client-service-worker/src/version.ts
@@ -1 +1 @@
-export default '7.13.15';
+export default '7.13.16';
diff --git a/packages/oidc-client/README.md b/packages/oidc-client/README.md
index b958d2bc0..2b9828817 100644
--- a/packages/oidc-client/README.md
+++ b/packages/oidc-client/README.md
@@ -205,8 +205,37 @@ const configuration = {
monitor_session: Boolean, // Add OpenID monitor session, default is false (more information https://openid.net/specs/openid-connect-session-1_0.html), if you need to set it to true consider https://infi.nl/nieuws/spa-necromancy/
token_renew_mode: String, // Optional, update tokens based on the selected token(s) lifetime: "access_token_or_id_token_invalid" (default), "access_token_invalid", "id_token_invalid"
logout_tokens_to_invalidate: Array
, // Optional tokens to invalidate during logout, default: ['access_token', 'refresh_token']
+ location: ILOidcLocation, // Optional, default is window.location, you can inject your own location object respecting the ILOidcLocation interface
demonstrating_proof_of_possession: Boolean, // Optional, default is false, if true, the the Demonstrating Proof of Possession will be activated //https://www.rfc-editor.org/rfc/rfc9449.html#name-protected-resource-access
+ demonstrating_proof_of_possession_configuration: DemonstratingProofOfPossessionConfiguration // Optional, more details bellow
};
+
+
+interface DemonstratingProofOfPossessionConfiguration {
+ generateKeyAlgorithm: RsaHashedKeyGenParams | EcKeyGenParams,
+ digestAlgorithm: AlgorithmIdentifier,
+ importKeyAlgorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
+ signAlgorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams,
+ jwtHeaderAlgorithm: string
+};
+
+// default value of demonstrating_proof_of_possession_configuration
+const defaultDemonstratingProofOfPossessionConfiguration: DemonstratingProofOfPossessionConfiguration ={
+ importKeyAlgorithm: {
+ name: 'ECDSA',
+ namedCurve: 'P-256',
+ hash: {name: 'ES256'}
+ },
+ signAlgorithm: {name: 'ECDSA', hash: {name: 'SHA-256'}},
+ generateKeyAlgorithm: {
+ name: 'ECDSA',
+ namedCurve: 'P-256'
+ },
+ digestAlgorithm: { name: 'SHA-256' },
+ jwtHeaderAlgorithm : 'ES256'
+};
+
+
```
## API
@@ -374,6 +403,8 @@ More information about OIDC
- [French : Augmentez la sécurité et la simplicité de votre Système d’Information OpenID Connect](https://medium.com/just-tech-it-now/augmentez-la-s%C3%A9curit%C3%A9-et-la-simplicit%C3%A9-de-votre-syst%C3%A8me-dinformation-avec-oauth-2-0-cf0732d71284)
- [English : Increase the security and simplicity of your information system with openid connect](https://medium.com/just-tech-it-now/increase-the-security-and-simplicity-of-your-information-system-with-openid-connect-fa8c26b99d6d)
+- [English: youtube OIDC](https://www.youtube.com/watch?v=frIJfavZkUE&list=PL8EMdIH6Mzxy2kHtsVOEWqNz-OaM_D_fB&index=1)
+- [French: youtube OIDC](https://www.youtube.com/watch?v=H-mLMGzQ_y0&list=PL8EMdIH6Mzxy2kHtsVOEWqNz-OaM_D_fB&index=2)
## Hash route
diff --git a/packages/oidc-client/package.json b/packages/oidc-client/package.json
index 352dd109f..55a894189 100644
--- a/packages/oidc-client/package.json
+++ b/packages/oidc-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@axa-fr/oidc-client",
- "version": "7.13.15",
+ "version": "7.13.16",
"private": false,
"type": "module",
"main": "./dist/index.umd.cjs",
diff --git a/packages/oidc-client/src/checkSession.ts b/packages/oidc-client/src/checkSession.ts
index 66124d9e9..e291ef2c1 100644
--- a/packages/oidc-client/src/checkSession.ts
+++ b/packages/oidc-client/src/checkSession.ts
@@ -1,9 +1,10 @@
import { CheckSessionIFrame } from './checkSessionIFrame.js';
import { _silentLoginAsync, SilentLoginResponse } from './silentLogin.js';
import { OidcConfiguration } from './types.js';
+import Oidc from "./oidc";
// eslint-disable-next-line @typescript-eslint/ban-types
-export const startCheckSessionAsync = (oidc:any, oidcDatabase:any, configuration :OidcConfiguration) => (checkSessionIFrameUri, clientId, sessionState, isSilentSignin = false) => {
+export const startCheckSessionAsync = (oidc:Oidc, oidcDatabase:any, configuration :OidcConfiguration) => (checkSessionIFrameUri, clientId, sessionState, isSilentSignin = false) => {
const silentLoginAsync = (extras, state = undefined, scope = undefined):Promise => {
return _silentLoginAsync(oidc.configurationName, configuration, oidc.publishEvent.bind(oidc))(extras, state, scope);
};
diff --git a/packages/oidc-client/src/initSession.ts b/packages/oidc-client/src/initSession.ts
index f582108b0..3d79b69b3 100644
--- a/packages/oidc-client/src/initSession.ts
+++ b/packages/oidc-client/src/initSession.ts
@@ -29,7 +29,7 @@ export const initSession = (configurationName, storage = sessionStorage) => {
storage[`oidc.nonce.${configurationName}`] = nonce.nonce;
};
- const setDemonstratingProofOfPossessionJwkAsync = (jwk) => {
+ const setDemonstratingProofOfPossessionJwkAsync = (jwk:JsonWebKey) => {
storage[`oidc.jwk.${configurationName}`] = JSON.stringify(jwk);
};
diff --git a/packages/oidc-client/src/initWorker.ts b/packages/oidc-client/src/initWorker.ts
index 959bc0a75..8a5d7d9ab 100644
--- a/packages/oidc-client/src/initWorker.ts
+++ b/packages/oidc-client/src/initWorker.ts
@@ -171,7 +171,7 @@ export const initWorkerAsync = async(configuration, configurationName) => {
return result.demonstratingProofOfPossessionNonce;
};
- const setDemonstratingProofOfPossessionJwkAsync = async (demonstratingProofOfPossessionJwk:string) => {
+ const setDemonstratingProofOfPossessionJwkAsync = async (demonstratingProofOfPossessionJwk:JsonWebKey) => {
const demonstratingProofOfPossessionJwkJson = JSON.stringify(demonstratingProofOfPossessionJwk);
sendMessageAsync(registration)({ type: 'setDemonstratingProofOfPossessionJwk', data: { demonstratingProofOfPossessionJwkJson }, configurationName });
};
diff --git a/packages/oidc-client/src/jwt.ts b/packages/oidc-client/src/jwt.ts
index c4a2c4286..2abe55a2b 100644
--- a/packages/oidc-client/src/jwt.ts
+++ b/packages/oidc-client/src/jwt.ts
@@ -4,6 +4,8 @@
//
// because... JavaScript, Strings, and Buffers
// @ts-ignore
+import {DemonstratingProofOfPossessionConfiguration} from "./types";
+
function strToUint8(str) {
return new TextEncoder().encode(str);
}
@@ -56,19 +58,42 @@ function strToUrlBase64(str) {
return binToUrlBase64(utf8ToBinaryString(str));
}
-export var JWT = {};
+export const defaultDemonstratingProofOfPossessionConfiguration: DemonstratingProofOfPossessionConfiguration ={
+ importKeyAlgorithm: {
+ name: 'ECDSA',
+ namedCurve: 'P-256',
+ hash: {name: 'ES256'}
+ },
+ signAlgorithm: {name: 'ECDSA', hash: {name: 'SHA-256'}},
+ generateKeyAlgorithm: {
+ name: 'ECDSA',
+ namedCurve: 'P-256'
+ },
+ digestAlgorithm: { name: 'SHA-256' },
+ jwtHeaderAlgorithm : 'ES256'
+}
+
+
// @ts-ignore
-JWT.sign = (jwk, headers, claims, jwtHeaderType= 'dpop+jwt') => {
+const sign = async (jwk, headers, claims, demonstratingProofOfPossessionConfiguration: DemonstratingProofOfPossessionConfiguration, jwtHeaderType= 'dpop+jwt') => {
// Make a shallow copy of the key
// (to set ext if it wasn't already set)
jwk = Object.assign({}, jwk);
// The headers should probably be empty
headers.typ = jwtHeaderType;
- headers.alg = 'ES256';
- if (!headers.kid) {
- // alternate: see thumbprint function below
- headers.jwk = { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y };
+ headers.alg = demonstratingProofOfPossessionConfiguration.jwtHeaderAlgorithm;
+ switch (headers.alg) {
+ case 'ES256': //if (!headers.kid) {
+ // alternate: see thumbprint function below
+ headers.jwk = {kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y};
+ //}
+ break;
+ case 'RS256':
+ headers.jwk = {kty: jwk.kty, n: jwk.n, e: jwk.e, kid: headers.kid};
+ break;
+ default:
+ throw new Error('Unknown or not implemented JWS algorithm');
}
const jws = {
@@ -81,11 +106,7 @@ JWT.sign = (jwk, headers, claims, jwtHeaderType= 'dpop+jwt') => {
};
// To import as EC (ECDSA, P-256, SHA-256, ES256)
- const keyType = {
- name: 'ECDSA',
- namedCurve: 'P-256',
- hash: {name: 'ES256'}
- };
+ const keyType = demonstratingProofOfPossessionConfiguration.importKeyAlgorithm;
// To make re-exportable as JSON (or DER/PEM)
const exportable = true;
@@ -95,80 +116,110 @@ JWT.sign = (jwk, headers, claims, jwtHeaderType= 'dpop+jwt') => {
// Actually do the import, which comes out as an abstract key type
// @ts-ignore
- return window.crypto.subtle
- // @ts-ignore
- .importKey('jwk', jwk, keyType, exportable, privileges)
- .then(function(privateKey) {
- // Convert UTF-8 to Uint8Array ArrayBuffer
- // @ts-ignore
- const data = strToUint8(jws.protected + '.' + jws.payload);
-
- // The signature and hash should match the bit-entropy of the key
- // https://tools.ietf.org/html/rfc7518#section-3
- const signatureType = {name: 'ECDSA', hash: {name: 'SHA-256'}};
-
- return window.crypto.subtle.sign(signatureType, privateKey, data).then(function(signature) {
- // returns an ArrayBuffer containing a JOSE (not X509) signature,
- // which must be converted to Uint8 to be useful
- // @ts-ignore
- jws.signature = uint8ToUrlBase64(new Uint8Array(signature));
-
- // JWT is just a "compressed", "protected" JWS
- // @ts-ignore
- return jws.protected + '.' + jws.payload + '.' + jws.signature;
- });
- });
+ const privateKey = await window.crypto.subtle.importKey('jwk', jwk, keyType, exportable, privileges);
+ // Convert UTF-8 to Uint8Array ArrayBuffer
+ // @ts-ignore
+ const data = strToUint8(`${jws.protected}.${jws.payload}`);
+
+ // The signature and hash should match the bit-entropy of the key
+ // https://tools.ietf.org/html/rfc7518#section-3
+ const signatureType = demonstratingProofOfPossessionConfiguration.signAlgorithm;
+
+ const signature = await window.crypto.subtle.sign(signatureType, privateKey, data);
+ // returns an ArrayBuffer containing a JOSE (not X509) signature,
+ // which must be converted to Uint8 to be useful
+ // @ts-ignore
+ jws.signature = uint8ToUrlBase64(new Uint8Array(signature));
+ // JWT is just a "compressed", "protected" JWS
+ // @ts-ignore
+ return `${jws.protected}.${jws.payload}.${jws.signature}`;
};
+export var JWT = {sign};
+
-const EC = {};
// @ts-ignore
-EC.generate = function() {
- const keyType = {
- name: 'ECDSA',
- namedCurve: 'P-256'
- };
+const generate = async (generateKeyAlgorithm: RsaHashedKeyGenParams | EcKeyGenParams) => {
+ const keyType = generateKeyAlgorithm;
const exportable = true;
const privileges = ['sign', 'verify'];
// @ts-ignore
- return window.crypto.subtle.generateKey(keyType, exportable, privileges).then(function(key) {
- // returns an abstract and opaque WebCrypto object,
- // which in most cases you'll want to export as JSON to be able to save
- return window.crypto.subtle.exportKey('jwk', key.privateKey);
- });
+ const key = await window.crypto.subtle.generateKey(keyType, exportable, privileges);
+ // returns an abstract and opaque WebCrypto object,
+ // which in most cases you'll want to export as JSON to be able to save
+ return await window.crypto.subtle.exportKey('jwk', key.privateKey);
};
// Create a Public Key from a Private Key
//
// chops off the private parts
// @ts-ignore
-EC.neuter = function(jwk) {
+const neuter = jwk => {
const copy = Object.assign({}, jwk);
delete copy.d;
copy.key_ops = ['verify'];
return copy;
};
-export var JWK = {};
+const EC = {
+ generate,
+ neuter
+};
// @ts-ignore
-JWK.thumbprint = function(jwk) {
+const thumbprint = async (jwk, digestAlgorithm: AlgorithmIdentifier) => {
+ let sortedPub;
// lexigraphically sorted, no spaces
- const sortedPub = '{"crv":"CRV","kty":"EC","x":"X","y":"Y"}'
- .replace('CRV', jwk.crv)
- .replace('X', jwk.x)
- .replace('Y', jwk.y);
-
+ switch (jwk.kty) {
+ case 'EC':
+ sortedPub = '{"crv":"CRV","kty":"EC","x":"X","y":"Y"}'
+ .replace('CRV', jwk.crv)
+ .replace('X', jwk.x)
+ .replace('Y', jwk.y);
+ break;
+ case 'RSA':
+ sortedPub = '{"e":"E","kty":"RSA","n":"N"}'
+ .replace('E', jwk.e)
+ .replace('N', jwk.n);
+ break;
+ default:
+ throw new Error('Unknown or not implemented JWK type');
+ }
// The hash should match the size of the key,
// but we're only dealing with P-256
- return window.crypto.subtle
- .digest({ name: 'SHA-256' }, strToUint8(sortedPub))
- .then(function(hash) {
- return uint8ToUrlBase64(new Uint8Array(hash));
- });
-};
+ const hash = await window.crypto.subtle.digest(digestAlgorithm, strToUint8(sortedPub));
+ return uint8ToUrlBase64(new Uint8Array(hash));
+}
+
+export var JWK = {thumbprint};
+
+export const generateJwkAsync = async (generateKeyAlgorithm: RsaHashedKeyGenParams | EcKeyGenParams) => {
+ // @ts-ignore
+ const jwk = await EC.generate(generateKeyAlgorithm);
+ console.info('Private Key:', JSON.stringify(jwk));
+ // @ts-ignore
+ console.info('Public Key:', JSON.stringify(EC.neuter(jwk)));
+ return jwk;
+}
+export const generateJwtDemonstratingProofOfPossessionAsync = (demonstratingProofOfPossessionConfiguration: DemonstratingProofOfPossessionConfiguration) => async (jwk, method = 'POST', url: string, extrasClaims={}) => {
-const guid = function () {
+ const claims = {
+ // https://www.rfc-editor.org/rfc/rfc9449.html#name-concept
+ jti: btoa(guid()),
+ htm: method,
+ htu: url,
+ iat: Math.round(Date.now() / 1000),
+ ...extrasClaims,
+ };
+ // @ts-ignore
+ const kid = await JWK.thumbprint(jwk, demonstratingProofOfPossessionConfiguration.digestAlgorithm);
+ // @ts-ignore
+ const jwt = await JWT.sign(jwk, { kid: kid }, claims, demonstratingProofOfPossessionConfiguration)
+ // console.info('JWT:', jwt);
+ return jwt;
+}
+
+const guid = () => {
// RFC4122: The version 4 UUID is meant for generating UUIDs from truly-random or
// pseudo-random numbers.
// The algorithm is as follows:
@@ -213,36 +264,3 @@ const guid = function () {
return guidResponse;
};
-
-
-export const generateJwkAsync = () => {
- // @ts-ignore
- return EC.generate().then(function(jwk) {
- // console.info('Private Key:', JSON.stringify(jwk));
- // @ts-ignore
- // console.info('Public Key:', JSON.stringify(EC.neuter(jwk)));
- return jwk;
- });
-}
-
-export const generateJwtDemonstratingProofOfPossessionAsync = (jwk, method = 'POST', url: string, extrasClaims={}) => {
-
- const claims = {
- // https://www.rfc-editor.org/rfc/rfc9449.html#name-concept
- jti: btoa(guid()),
- htm: method,
- htu: url,
- iat: Math.round(Date.now() / 1000),
- ...extrasClaims,
- };
- // @ts-ignore
- return JWK.thumbprint(jwk).then(function(kid) {
- // @ts-ignore
- return JWT.sign(jwk, { /*kid: kid*/ }, claims).then(function(jwt) {
- // console.info('JWT:', jwt);
- return jwt;
- });
- });
-}
-
-export default EC;
\ No newline at end of file
diff --git a/packages/oidc-client/src/keepSession.ts b/packages/oidc-client/src/keepSession.ts
new file mode 100644
index 000000000..018733877
--- /dev/null
+++ b/packages/oidc-client/src/keepSession.ts
@@ -0,0 +1,78 @@
+import {initWorkerAsync} from "./initWorker";
+import {autoRenewTokens} from "./renewTokens";
+import {initSession} from "./initSession";
+import {setTokens} from "./parseTokens";
+import {eventNames} from "./events";
+import Oidc from "./oidc";
+
+export const tryKeepSessionAsync = async (oidc: Oidc) =>{
+
+ let serviceWorker;
+ if (oidc.tokens != null) {
+ return false;
+ }
+ oidc.publishEvent(eventNames.tryKeepExistingSessionAsync_begin, {});
+ try {
+ const configuration = oidc.configuration;
+ const oidcServerConfiguration = await oidc.initAsync(configuration.authority, configuration.authority_configuration);
+ serviceWorker = await initWorkerAsync(configuration, oidc.configurationName);
+ if (serviceWorker) {
+ const { tokens } = await serviceWorker.initAsync(oidcServerConfiguration, 'tryKeepExistingSessionAsync', configuration);
+ if (tokens) {
+ serviceWorker.startKeepAliveServiceWorker();
+ // @ts-ignore
+ oidc.tokens = tokens;
+ const getLoginParams = serviceWorker.getLoginParams(oidc.configurationName);
+ // @ts-ignore
+ oidc.timeoutId = autoRenewTokens(oidc, oidc.tokens.refreshToken, oidc.tokens.expiresAt, getLoginParams.extras);
+ const sessionState = await serviceWorker.getSessionStateAsync();
+ // @ts-ignore
+ await oidc.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
+ oidc.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
+ success: true,
+ message: 'tokens inside ServiceWorker are valid',
+ });
+ return true;
+ }
+ oidc.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
+ success: false,
+ message: 'no exiting session found',
+ });
+ } else {
+ if (configuration.service_worker_relative_url) {
+ oidc.publishEvent(eventNames.service_worker_not_supported_by_browser, {
+ message: 'service worker is not supported by this browser',
+ });
+ }
+ const session = initSession(oidc.configurationName, configuration.storage ?? sessionStorage);
+ const { tokens } = await session.initAsync();
+ if (tokens) {
+ // @ts-ignore
+ oidc.tokens = setTokens(tokens, null, configuration.token_renew_mode);
+ const getLoginParams = session.getLoginParams();
+ // @ts-ignore
+ oidc.timeoutId = autoRenewTokens(oidc, tokens.refreshToken, oidc.tokens.expiresAt, getLoginParams.extras);
+ const sessionState = await session.getSessionStateAsync();
+ // @ts-ignore
+ await oidc.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
+ oidc.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
+ success: true,
+ message: 'tokens inside storage are valid',
+ });
+ return true;
+ }
+ }
+ oidc.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
+ success: false,
+ message: serviceWorker ? 'service worker sessions not retrieved' : 'session storage sessions not retrieved',
+ });
+ return false;
+ } catch (exception) {
+ console.error(exception);
+ if (serviceWorker) {
+ await serviceWorker.clearAsync();
+ }
+ oidc.publishEvent(eventNames.tryKeepExistingSessionAsync_error, 'tokens inside ServiceWorker are invalid');
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/packages/oidc-client/src/login.ts b/packages/oidc-client/src/login.ts
index 471149e50..473c14055 100644
--- a/packages/oidc-client/src/login.ts
+++ b/packages/oidc-client/src/login.ts
@@ -11,6 +11,7 @@ import {getParseQueryStringFromLocation} from './route-utils.js';
import {OidcConfiguration, StringMap} from './types.js';
import {generateJwkAsync, generateJwtDemonstratingProofOfPossessionAsync} from "./jwt";
import {ILOidcLocation} from "./location";
+import Oidc from "./oidc";
// eslint-disable-next-line @typescript-eslint/ban-types
export const defaultLoginAsync = (configurationName:string, configuration:OidcConfiguration, publishEvent :(string, any)=>void, initAsync:Function, oidcLocation: ILOidcLocation) => (callbackPath:string = undefined, extras:StringMap = null, isSilentSignin = false, scope:string = undefined) => {
@@ -75,7 +76,7 @@ export const defaultLoginAsync = (configurationName:string, configuration:OidcCo
return loginLocalAsync();
};
-export const loginCallbackAsync = (oidc) => async (isSilentSignin = false) => {
+export const loginCallbackAsync = (oidc:Oidc) => async (isSilentSignin = false) => {
try {
oidc.publishEvent(eventNames.loginCallbackAsync_begin, {});
const configuration = oidc.configuration;
@@ -148,14 +149,14 @@ export const loginCallbackAsync = (oidc) => async (isSilentSignin = false) => {
const url = oidcServerConfiguration.tokenEndpoint;
const headersExtras = {};
if(configuration.demonstrating_proof_of_possession) {
- const jwk = await generateJwkAsync();
+ const jwk = await generateJwkAsync(configuration.demonstrating_proof_of_possession_configuration.generateKeyAlgorithm);
if (serviceWorker) {
await serviceWorker.setDemonstratingProofOfPossessionJwkAsync(jwk);
} else {
const session = initSession(oidc.configurationName, configuration.storage);
await session.setDemonstratingProofOfPossessionJwkAsync(jwk);
}
- headersExtras['DPoP'] = await generateJwtDemonstratingProofOfPossessionAsync(jwk, 'POST', url);
+ headersExtras['DPoP'] = await generateJwtDemonstratingProofOfPossessionAsync(configuration.demonstrating_proof_of_possession_configuration)(jwk, 'POST', url);
}
const tokenResponse = await performFirstTokenRequestAsync(storage)(url,
diff --git a/packages/oidc-client/src/logout.ts b/packages/oidc-client/src/logout.ts
index 782984f06..33116c865 100644
--- a/packages/oidc-client/src/logout.ts
+++ b/packages/oidc-client/src/logout.ts
@@ -4,6 +4,7 @@ import { performRevocationRequestAsync, TOKEN_TYPE } from './requests.js';
import timer from './timer.js';
import { StringMap } from './types.js';
import {ILOidcLocation} from "./location";
+import Oidc from "./oidc";
export const oidcLogoutTokens = {
access_token: 'access_token',
diff --git a/packages/oidc-client/src/oidc.ts b/packages/oidc-client/src/oidc.ts
index eb55d301b..bbd925d0b 100644
--- a/packages/oidc-client/src/oidc.ts
+++ b/packages/oidc-client/src/oidc.ts
@@ -5,8 +5,13 @@ import {initSession} from './initSession.js';
import {defaultServiceWorkerUpdateRequireCallback, initWorkerAsync, sleepAsync} from './initWorker.js';
import {defaultLoginAsync, loginCallbackAsync} from './login.js';
import {destroyAsync, logoutAsync} from './logout.js';
-import {computeTimeLeft, isTokensOidcValid, setTokens, TokenRenewMode, Tokens,} from './parseTokens.js';
-import {autoRenewTokens, renewTokensAndStartTimerAsync} from './renewTokens.js';
+import {isTokensOidcValid, TokenRenewMode, Tokens,} from './parseTokens.js';
+import {
+ autoRenewTokens,
+ renewTokensAndStartTimerAsync,
+ synchroniseTokensStatus,
+ syncTokensInfoAsync
+} from './renewTokens.js';
import {fetchFromIssuer, performTokenRequestAsync} from './requests.js';
import {getParseQueryStringFromLocation} from './route-utils.js';
import defaultSilentLoginAsync, {_silentLoginAsync} from './silentLogin.js';
@@ -14,9 +19,13 @@ import timer from './timer.js';
import {AuthorityConfiguration, Fetch, OidcConfiguration, StringMap} from './types.js';
import {userInfoAsync} from './user.js';
import {base64urlOfHashOfASCIIEncodingAsync} from "./crypto";
-import {generateJwtDemonstratingProofOfPossessionAsync} from "./jwt";
+import {
+ defaultDemonstratingProofOfPossessionConfiguration,
+ generateJwtDemonstratingProofOfPossessionAsync
+} from "./jwt";
import {ILOidcLocation, OidcLocation} from "./location";
import {activateServiceWorker} from "./initWorkerOption";
+import {tryKeepSessionAsync} from "./keepSession";
@@ -83,11 +92,11 @@ export class Oidc {
public userInfo: null;
public tokens?: Tokens;
public events: Array;
- private timeoutId: NodeJS.Timeout;
+ public timeoutId: NodeJS.Timeout | number;
public configurationName: string;
- private checkSessionIFrame: CheckSessionIFrame;
+ public checkSessionIFrame: CheckSessionIFrame;
private getFetch: () => Fetch;
- private location: ILOidcLocation;
+ public location: ILOidcLocation;
constructor(configuration:OidcConfiguration, configurationName = 'default', getFetch : () => Fetch, location: ILOidcLocation = new OidcLocation()) {
let silent_login_uri = configuration.silent_login_uri;
if (configuration.silent_redirect_uri && !configuration.silent_login_uri) {
@@ -112,6 +121,7 @@ export class Oidc {
logout_tokens_to_invalidate: configuration.logout_tokens_to_invalidate ?? ['access_token', 'refresh_token'],
service_worker_update_require_callback,
service_worker_activate: configuration.service_worker_activate ?? activateServiceWorker,
+ demonstrating_proof_of_possession_configuration: configuration.demonstrating_proof_of_possession_configuration ?? defaultDemonstratingProofOfPossessionConfiguration,
};
this.getFetch = getFetch ?? getFetchDefault;
@@ -225,78 +235,8 @@ Please checkout that you are using OIDC hook inside a {
- let serviceWorker;
- if (this.tokens != null) {
- return false;
- }
- this.publishEvent(eventNames.tryKeepExistingSessionAsync_begin, {});
- try {
- const configuration = this.configuration;
- const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
- serviceWorker = await initWorkerAsync(configuration, this.configurationName);
- if (serviceWorker) {
- const { tokens } = await serviceWorker.initAsync(oidcServerConfiguration, 'tryKeepExistingSessionAsync', configuration);
- if (tokens) {
- serviceWorker.startKeepAliveServiceWorker();
- // @ts-ignore
- this.tokens = tokens;
- const getLoginParams = serviceWorker.getLoginParams(this.configurationName);
- // @ts-ignore
- this.timeoutId = autoRenewTokens(this, this.tokens.refreshToken, this.tokens.expiresAt, getLoginParams.extras);
- const sessionState = await serviceWorker.getSessionStateAsync();
- // @ts-ignore
- await this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
- this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
- success: true,
- message: 'tokens inside ServiceWorker are valid',
- });
- return true;
- }
- this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
- success: false,
- message: 'no exiting session found',
- });
- } else {
- if (configuration.service_worker_relative_url) {
- this.publishEvent(eventNames.service_worker_not_supported_by_browser, {
- message: 'service worker is not supported by this browser',
- });
- }
- const session = initSession(this.configurationName, configuration.storage ?? sessionStorage);
- const { tokens } = await session.initAsync();
- if (tokens) {
- // @ts-ignore
- this.tokens = setTokens(tokens, null, configuration.token_renew_mode);
- const getLoginParams = session.getLoginParams();
- // @ts-ignore
- this.timeoutId = autoRenewTokens(this, tokens.refreshToken, this.tokens.expiresAt, getLoginParams.extras);
- const sessionState = await session.getSessionStateAsync();
- // @ts-ignore
- await this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
- this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
- success: true,
- message: 'tokens inside storage are valid',
- });
- return true;
- }
- }
- this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
- success: false,
- message: serviceWorker ? 'service worker sessions not retrieved' : 'session storage sessions not retrieved',
- });
- return false;
- } catch (exception) {
- console.error(exception);
- if (serviceWorker) {
- await serviceWorker.clearAsync();
- }
- this.publishEvent(eventNames.tryKeepExistingSessionAsync_error, 'tokens inside ServiceWorker are invalid');
- return false;
- }
- };
-
- this.tryKeepExistingSessionPromise = funcAsync();
+
+ this.tryKeepExistingSessionPromise = tryKeepSessionAsync(this);
return this.tryKeepExistingSessionPromise.then((result) => {
this.tryKeepExistingSessionPromise = null;
return result;
@@ -349,8 +289,11 @@ Please checkout that you are using OIDC hook inside a 0) ? 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID' : 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_INVALID';
- const nonce = await serviceWorker.getNonceAsync();
- return { tokens, status, nonce };
- }
- nonce = await serviceWorker.getNonceAsync();
- } else {
- const session = initSession(configurationName, configuration.storage ?? sessionStorage);
- const { tokens, status } = await session.initAsync();
- if (!tokens) {
- return { tokens: null, status: 'LOGOUT_FROM_ANOTHER_TAB', nonce: nullNonce };
- } else if (status === 'SESSIONS_LOST') {
- return { tokens: null, status: 'SESSIONS_LOST', nonce: nullNonce };
- } else if (tokens.issuedAt !== currentTokens.issuedAt) {
- const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, tokens.expiresAt);
- const status = (timeLeft > 0) ? 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID' : 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_INVALID';
- const nonce = await session.getNonceAsync();
- return { tokens, status, nonce };
- }
- nonce = await session.getNonceAsync();
- }
-
- const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, currentTokens.expiresAt);
- const status = (timeLeft > 0) ? 'TOKENS_VALID' : 'TOKENS_INVALID';
- if (forceRefresh) {
- return { tokens: currentTokens, status: 'FORCE_REFRESH', nonce };
- }
- return { tokens: currentTokens, status, nonce };
+ return await generateJwtDemonstratingProofOfPossessionAsync(configuration.demonstrating_proof_of_possession_configuration)(jwk, method, url, claimsExtras);
}
loginCallbackWithAutoTokensRenewPromise:Promise = null;
diff --git a/packages/oidc-client/src/parseTokens.ts b/packages/oidc-client/src/parseTokens.ts
index 0df18cb16..d79f35846 100644
--- a/packages/oidc-client/src/parseTokens.ts
+++ b/packages/oidc-client/src/parseTokens.ts
@@ -20,7 +20,7 @@ const extractTokenPayload = (token:string) => {
return null;
};
-const countLetter = (str, find) => {
+const countLetter = (str : string, find) => {
return (str.split(find)).length - 1;
};
diff --git a/packages/oidc-client/src/renewTokens.ts b/packages/oidc-client/src/renewTokens.ts
index 8d80ebb5b..db7fd883b 100644
--- a/packages/oidc-client/src/renewTokens.ts
+++ b/packages/oidc-client/src/renewTokens.ts
@@ -3,9 +3,9 @@ import {initWorkerAsync} from './initWorker.js';
import Oidc from './oidc.js';
import {computeTimeLeft, setTokens} from './parseTokens.js';
import timer from './timer.js';
-import {StringMap} from './types.js';
+import {OidcConfiguration, StringMap} from './types.js';
-async function syncTokens(oidc, refreshToken, forceRefresh: boolean, extras: StringMap) {
+async function syncTokens(oidc:Oidc, refreshToken, forceRefresh: boolean, extras: StringMap) {
const updateTokens = (tokens) => {
oidc.tokens = tokens;
};
@@ -72,7 +72,7 @@ export async function renewTokensAndStartTimerAsync(oidc, refreshToken, forceRef
return oidc.tokens;
}
-export const autoRenewTokens = (oidc, refreshToken, expiresAt, extras:StringMap = null) => {
+export const autoRenewTokens = (oidc:Oidc, refreshToken, expiresAt, extras:StringMap = null) => {
const refreshTimeBeforeTokensExpirationInSecond = oidc.configuration.refresh_time_before_tokens_expiration_in_second;
return timer.setTimeout(async () => {
const timeLeft = computeTimeLeft(refreshTimeBeforeTokensExpirationInSecond, expiresAt);
@@ -81,3 +81,61 @@ export const autoRenewTokens = (oidc, refreshToken, expiresAt, extras:StringMap
await renewTokensAndStartTimerAsync(oidc, refreshToken, false, extras);
}, 1000);
};
+
+export const synchroniseTokensStatus ={
+ 'SESSION_LOST': 'SESSION_LOST',
+ 'NOT_CONNECTED':'NOT_CONNECTED',
+ 'TOKENS_VALID':'TOKENS_VALID',
+ 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID': 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID',
+ 'LOGOUT_FROM_ANOTHER_TAB': 'LOGOUT_FROM_ANOTHER_TAB',
+ 'REQUIRE_SYNC_TOKENS': 'REQUIRE_SYNC_TOKENS'
+};
+
+export const syncTokensInfoAsync = (oidc: Oidc) => async (configuration:OidcConfiguration, configurationName: string, currentTokens, forceRefresh = false) => {
+ // 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)
+ // const configuration = this.configuration;
+ const nullNonce = { nonce: null };
+ if (!currentTokens) {
+ return { tokens: null, status: 'NOT_CONNECTED', nonce: nullNonce };
+ }
+ let nonce = nullNonce;
+ const oidcServerConfiguration = await oidc.initAsync(configuration.authority, configuration.authority_configuration);
+ const serviceWorker = await initWorkerAsync(configuration, configurationName);
+ if (serviceWorker) {
+ const { status, tokens } = await serviceWorker.initAsync(oidcServerConfiguration, 'syncTokensAsync', configuration);
+ if (status === 'LOGGED_OUT') {
+ return { tokens: null, status: 'LOGOUT_FROM_ANOTHER_TAB', nonce: nullNonce };
+ } else if (status === 'SESSIONS_LOST') {
+ return { tokens: null, status: 'SESSIONS_LOST', nonce: nullNonce };
+ } else if (!status || !tokens) {
+ return { tokens: null, status: 'REQUIRE_SYNC_TOKENS', nonce: nullNonce };
+ } else if (tokens.issuedAt !== currentTokens.issuedAt) {
+ const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, tokens.expiresAt);
+ const status = (timeLeft > 0) ? 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID' : 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_INVALID';
+ const nonce = await serviceWorker.getNonceAsync();
+ return { tokens, status, nonce };
+ }
+ nonce = await serviceWorker.getNonceAsync();
+ } else {
+ const session = initSession(configurationName, configuration.storage ?? sessionStorage);
+ const { tokens, status } = await session.initAsync();
+ if (!tokens) {
+ return { tokens: null, status: 'LOGOUT_FROM_ANOTHER_TAB', nonce: nullNonce };
+ } else if (status === 'SESSIONS_LOST') {
+ return { tokens: null, status: 'SESSIONS_LOST', nonce: nullNonce };
+ } else if (tokens.issuedAt !== currentTokens.issuedAt) {
+ const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, tokens.expiresAt);
+ const status = (timeLeft > 0) ? 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID' : 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_INVALID';
+ const nonce = await session.getNonceAsync();
+ return { tokens, status, nonce };
+ }
+ nonce = await session.getNonceAsync();
+ }
+
+ const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, currentTokens.expiresAt);
+ const status = (timeLeft > 0) ? 'TOKENS_VALID' : 'TOKENS_INVALID';
+ if (forceRefresh) {
+ return { tokens: currentTokens, status: 'FORCE_REFRESH', nonce };
+ }
+ return { tokens: currentTokens, status, nonce };
+}
\ No newline at end of file
diff --git a/packages/oidc-client/src/requests.ts b/packages/oidc-client/src/requests.ts
index 176a25c63..98d1272ae 100644
--- a/packages/oidc-client/src/requests.ts
+++ b/packages/oidc-client/src/requests.ts
@@ -27,7 +27,7 @@ export const fetchFromIssuer = (fetch) => async (openIdIssuerUrl: string, timeCa
return new OidcAuthorizationServiceConfiguration(result);
};
-const internalFetch = (fetch) => async (url, headers = {}, timeoutMs = 10000, numberRetry = 0) : Promise => {
+const internalFetch = (fetch) => async (url:string, headers = {}, timeoutMs = 10000, numberRetry = 0) : Promise => {
let response;
try {
const controller = new AbortController();
diff --git a/packages/oidc-client/src/types.ts b/packages/oidc-client/src/types.ts
index 54180c818..c823c772e 100644
--- a/packages/oidc-client/src/types.ts
+++ b/packages/oidc-client/src/types.ts
@@ -33,8 +33,17 @@ export type OidcConfiguration = {
token_renew_mode?: string;
logout_tokens_to_invalidate?:Array;
demonstrating_proof_of_possession?:boolean;
+ demonstrating_proof_of_possession_configuration?: DemonstratingProofOfPossessionConfiguration;
};
+export interface DemonstratingProofOfPossessionConfiguration {
+ generateKeyAlgorithm: RsaHashedKeyGenParams | EcKeyGenParams,
+ digestAlgorithm: AlgorithmIdentifier,
+ importKeyAlgorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
+ signAlgorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams,
+ jwtHeaderAlgorithm: string
+}
+
export interface StringMap {
[key: string]: string;
}
diff --git a/packages/oidc-client/src/user.ts b/packages/oidc-client/src/user.ts
index 5068c6435..b523bab30 100644
--- a/packages/oidc-client/src/user.ts
+++ b/packages/oidc-client/src/user.ts
@@ -1,7 +1,8 @@
import { sleepAsync } from './initWorker.js';
import { isTokensValid } from './parseTokens.js';
+import Oidc from "./oidc";
-export const userInfoAsync = (oidc) => async (noCache = false) => {
+export const userInfoAsync = (oidc:Oidc) => async (noCache = false) => {
if (oidc.userInfo != null && !noCache) {
return oidc.userInfo;
}
diff --git a/packages/oidc-client/src/version.ts b/packages/oidc-client/src/version.ts
index 8e8511c92..52e8c9412 100644
--- a/packages/oidc-client/src/version.ts
+++ b/packages/oidc-client/src/version.ts
@@ -1 +1 @@
-export default '7.13.15';
+export default '7.13.16';
diff --git a/packages/react-oidc/README.md b/packages/react-oidc/README.md
index 8abaddeb2..00c30601b 100644
--- a/packages/react-oidc/README.md
+++ b/packages/react-oidc/README.md
@@ -202,11 +202,41 @@ const configuration = {
onLogoutFromSameTab: Function, // Optional, can be set to override the default behavior, this function is triggered when a user is logged out from the same tab when session_monitor is active
token_renew_mode: String, // Optional, update tokens based on the selected token(s) lifetime: "access_token_or_id_token_invalid" (default), "access_token_invalid", "id_token_invalid"
logout_tokens_to_invalidate: Array, // Optional tokens to invalidate during logout, default: ['access_token', 'refresh_token']
- demonstrating_proof_of_possession: Boolean, // Optional, default is false, if true, the the Demonstrating Proof of Possession will be activated //https://www.rfc-editor.org/rfc/rfc9449.html#name-protected-resource-access
location: ILOidcLocation, // Optional, default is window.location, you can inject your own location object respecting the ILOidcLocation interface
- }.isRequired,
+ demonstrating_proof_of_possession: Boolean, // Optional, default is false, if true, the the Demonstrating Proof of Possession will be activated //https://www.rfc-editor.org/rfc/rfc9449.html#name-protected-resource-access
+ demonstrating_proof_of_possession_configuration: DemonstratingProofOfPossessionConfiguration // Optional, more details bellow
+ },
};
+demonstrating_proof_of_possession_configuration: DemonstratingProofOfPossessionConfiguration // Optional, more details bellow
+};
+
+
+interface DemonstratingProofOfPossessionConfiguration {
+ generateKeyAlgorithm: RsaHashedKeyGenParams | EcKeyGenParams,
+ digestAlgorithm: AlgorithmIdentifier,
+ importKeyAlgorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
+ signAlgorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams,
+ jwtHeaderAlgorithm: string
+};
+
+// default value of demonstrating_proof_of_possession_configuration
+const defaultDemonstratingProofOfPossessionConfiguration: DemonstratingProofOfPossessionConfiguration ={
+ importKeyAlgorithm: {
+ name: 'ECDSA',
+ namedCurve: 'P-256',
+ hash: {name: 'ES256'}
+ },
+ signAlgorithm: {name: 'ECDSA', hash: {name: 'SHA-256'}},
+ generateKeyAlgorithm: {
+ name: 'ECDSA',
+ namedCurve: 'P-256'
+ },
+ digestAlgorithm: { name: 'SHA-256' },
+ jwtHeaderAlgorithm : 'ES256'
+};
+
+
```
## How to consume
@@ -590,6 +620,8 @@ More information about OIDC
- [French : Augmentez la sécurité et la simplicité de votre Système d’Information OpenID Connect](https://medium.com/just-tech-it-now/augmentez-la-s%C3%A9curit%C3%A9-et-la-simplicit%C3%A9-de-votre-syst%C3%A8me-dinformation-avec-oauth-2-0-cf0732d71284)
- [English : Increase the security and simplicity of your information system with openid connect](https://medium.com/just-tech-it-now/increase-the-security-and-simplicity-of-your-information-system-with-openid-connect-fa8c26b99d6d)
+- [English: youtube OIDC](https://www.youtube.com/watch?v=frIJfavZkUE&list=PL8EMdIH6Mzxy2kHtsVOEWqNz-OaM_D_fB&index=1)
+- [French: youtube OIDC](https://www.youtube.com/watch?v=H-mLMGzQ_y0&list=PL8EMdIH6Mzxy2kHtsVOEWqNz-OaM_D_fB&index=2)
## NextJS
diff --git a/packages/react-oidc/package.json b/packages/react-oidc/package.json
index 551ee52da..4beaeb130 100644
--- a/packages/react-oidc/package.json
+++ b/packages/react-oidc/package.json
@@ -1,6 +1,6 @@
{
"name": "@axa-fr/react-oidc",
- "version": "7.13.15",
+ "version": "7.13.16",
"private": false,
"type": "module",
"main": "./dist/index.umd.cjs",
diff --git a/readme.md b/readme.md
index 7efd04620..f257e5d4d 100644
--- a/readme.md
+++ b/readme.md
@@ -160,7 +160,8 @@ More information about OIDC :
- [French : Augmentez la sécurité et la simplicité de votre Système d’Information OpenID Connect](https://medium.com/just-tech-it-now/augmentez-la-s%C3%A9curit%C3%A9-et-la-simplicit%C3%A9-de-votre-syst%C3%A8me-dinformation-avec-oauth-2-0-cf0732d71284)
- [English : Increase the security and simplicity of your information system with OpenID Connect](https://medium.com/just-tech-it-now/increase-the-security-and-simplicity-of-your-information-system-with-openid-connect-fa8c26b99d6d)
-
+- [English: youtube OIDC](https://www.youtube.com/watch?v=frIJfavZkUE&list=PL8EMdIH6Mzxy2kHtsVOEWqNz-OaM_D_fB&index=1)
+- [French: youtube OIDC](https://www.youtube.com/watch?v=H-mLMGzQ_y0&list=PL8EMdIH6Mzxy2kHtsVOEWqNz-OaM_D_fB&index=2)
## FAQ
- Frequented Asked Question [`FAQ`](./FAQ.md)