From d7d2c149cff306679696c3cf47aeffae364a0fe5 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 25 Jan 2024 20:02:52 +0000 Subject: [PATCH 1/3] [skip ci] Update to version 7.13.16 in package.json --- packages/oidc-client-service-worker/package.json | 2 +- packages/oidc-client-service-worker/src/version.ts | 2 +- packages/oidc-client/package.json | 2 +- packages/oidc-client/src/version.ts | 2 +- packages/react-oidc/package.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) 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/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/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/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", From df754bd51e94bce277d7b5baaadc109042ad8c89 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 25 Jan 2024 20:02:53 +0000 Subject: [PATCH 2/3] [skip ci] Generate changelog to version 7.13.16 --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b862a4111..5f2755cb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog -## 7.13.15 +## 7.13.16 + +- [f2310bc](https://github.com/AxaFrance/oidc-client/commit/f2310bc917898b779a40edecf3dd73c21ec32b47) - fix(oidc): parseJwt fails for some JWT because of wrong use of replace (release) (#1269), 2024-01-25 by *Guillaume Chervet* + + +## v7.13.15 - [c56cc84](https://github.com/AxaFrance/oidc-client/commit/c56cc842d8da427b2aa88eb71f7d63937ea3c363) - fix(react-oidc): missing console.log in useOidcFetch (release), 2024-01-16 by *Guillaume Chervet* @@ -314,8 +319,3 @@ - [9c7a143](https://github.com/AxaFrance/oidc-client/commit/9c7a143797cffff1044c13787c6ceaf690fa4c87) - fix build (release), 2023-07-28 by *Guillaume Chervet* -## v6.26.3 - -- [04f4900](https://github.com/AxaFrance/oidc-client/commit/04f4900d672e954278e6835bc31c62e4bf8660de) - try fix (release), 2023-07-28 by *Guillaume Chervet* - - From b0510eb8d7170e8e142cb2d9bec01f41f610ed51 Mon Sep 17 00:00:00 2001 From: Guillaume Chervet Date: Fri, 26 Jan 2024 12:53:54 +0100 Subject: [PATCH 3/3] feat(oidc): add DPOP configuration (#1259) (release) --- .../public/OidcTrustedDomains.js | 2 + examples/react-oidc-demo/src/FetchUser.tsx | 4 +- examples/react-oidc-demo/src/MultiAuth.tsx | 44 +++- packages/oidc-client/README.md | 31 +++ packages/oidc-client/src/checkSession.ts | 3 +- packages/oidc-client/src/initSession.ts | 2 +- packages/oidc-client/src/initWorker.ts | 2 +- packages/oidc-client/src/jwt.ts | 204 ++++++++++-------- packages/oidc-client/src/keepSession.ts | 78 +++++++ packages/oidc-client/src/login.ts | 7 +- packages/oidc-client/src/logout.ts | 1 + packages/oidc-client/src/oidc.ts | 166 +++----------- packages/oidc-client/src/parseTokens.ts | 2 +- packages/oidc-client/src/renewTokens.ts | 64 +++++- packages/oidc-client/src/requests.ts | 2 +- packages/oidc-client/src/types.ts | 9 + packages/oidc-client/src/user.ts | 3 +- packages/react-oidc/README.md | 36 +++- readme.md | 3 +- 19 files changed, 408 insertions(+), 255 deletions(-) create mode 100644 packages/oidc-client/src/keepSession.ts diff --git a/examples/react-oidc-demo/public/OidcTrustedDomains.js b/examples/react-oidc-demo/public/OidcTrustedDomains.js index 5bc49de89..416c4626a 100644 --- a/examples/react-oidc-demo/public/OidcTrustedDomains.js +++ b/examples/react-oidc-demo/public/OidcTrustedDomains.js @@ -21,4 +21,6 @@ trustedDomains.config_separate_oidc_access_token_domains = { oidcDomains: ["https://demo.duendesoftware.com"], accessTokenDomains: ["https://myapi"] }; + +trustedDomains.config_with_dpop = { domains: ["https://demo.duendesoftware.com"], showAccessToken: true }; //# sourceMappingURL=OidcTrustedDomains.js.map \ No newline at end of file diff --git a/examples/react-oidc-demo/src/FetchUser.tsx b/examples/react-oidc-demo/src/FetchUser.tsx index b4178363e..cc4795aa5 100644 --- a/examples/react-oidc-demo/src/FetchUser.tsx +++ b/examples/react-oidc-demo/src/FetchUser.tsx @@ -46,7 +46,7 @@ const UserInfoWithFetchHoc = withOidcFetch(fetch)(DisplayUserInfo); export const FetchUserHoc = () => ; -export const FetchUserHook = () => { - const { fetch } = useOidcFetch(); +export const FetchUserHook = (props:any) => { + const { fetch } = useOidcFetch(window.fetch, props.configurationName); return ; }; diff --git a/examples/react-oidc-demo/src/MultiAuth.tsx b/examples/react-oidc-demo/src/MultiAuth.tsx index 38ee6663f..77e83b461 100644 --- a/examples/react-oidc-demo/src/MultiAuth.tsx +++ b/examples/react-oidc-demo/src/MultiAuth.tsx @@ -8,6 +8,7 @@ import { CallBackSuccess } from './override/Callback.component'; import Loading from './override/Loading.component'; import ServiceWorkerNotSupported from './override/ServiceWorkerNotSupported.component'; import SessionLost from './override/SessionLost.component'; +import {FetchUserHook} from "./FetchUser"; const fetchWithLogs = (fetch: Fetch) => async (...params: Parameters) => { const [url, options, ...rest] = params; @@ -45,6 +46,7 @@ const MultiAuth = ({ configurationName, handleConfigurationChange }) => { + {!isAuthenticated && } {isAuthenticatedDefault && } @@ -118,6 +120,27 @@ export const MultiAuthContainer = () => { redirect_uri: callBack, silent_redirect_uri, }, + config_with_dpop: { + ...configurationIdentityServer, + redirect_uri: callBack, + silent_redirect_uri, + demonstrating_proof_of_possession: true, + /*demonstrating_proof_of_possession_configuration: { + importKeyAlgorithm: { + name: "RSASSA-PKCS1-v1_5", + hash: { name: "SHA-256" }, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512" + }, + signAlgorithm: { name: "RSASSA-PKCS1-v1_5" }, + generateKeyAlgorithm: { + name: "RSASSA-PKCS1-v1_5", + modulusLength: 2048, //can be 1024, 2048, or 4096 + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: { name: "SHA-256" }, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512" + }, + digestAlgorithm: { name: "SHA-256" }, + jwtHeaderAlgorithm: "RS256", + },*/ + } }; const handleConfigurationChange = (event) => { const configurationName = event.target.value; @@ -176,15 +199,18 @@ const DisplayAccessToken = ({ configurationName }) => { return

you are not authentified

; } return ( -
-
-
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/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/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/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/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)