diff --git a/examples/react-oidc-demo/public/OidcTrustedDomains.js b/examples/react-oidc-demo/public/OidcTrustedDomains.js index e7b4ee1c1..933b0f2bc 100644 --- a/examples/react-oidc-demo/public/OidcTrustedDomains.js +++ b/examples/react-oidc-demo/public/OidcTrustedDomains.js @@ -24,5 +24,7 @@ trustedDomains.config_separate_oidc_access_token_domains = { trustedDomains.config_with_dpop = { domains: ["https://demo.duendesoftware.com"], - demonstratingProofOfPossession: true }; + demonstratingProofOfPossession: true, + demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent: true +}; //# sourceMappingURL=OidcTrustedDomains.js.map \ No newline at end of file diff --git a/packages/oidc-client-service-worker/src/OidcServiceWorker.ts b/packages/oidc-client-service-worker/src/OidcServiceWorker.ts index 22271eb52..cb3b7ba93 100644 --- a/packages/oidc-client-service-worker/src/OidcServiceWorker.ts +++ b/packages/oidc-client-service-worker/src/OidcServiceWorker.ts @@ -18,7 +18,7 @@ import {extractConfigurationNameFromCodeVerifier, replaceCodeVerifier} from './u import { normalizeUrl } from './utils/normalizeUrl'; import version from './version'; import {generateJwkAsync, generateJwtDemonstratingProofOfPossessionAsync} from "./jwt"; -import {getDpopConfiguration} from "./dpop"; +import {getDpopConfiguration, getDpopOnlyWhenDpopHeaderPresent} from "./dpop"; import {base64urlOfHashOfASCIIEncodingAsync} from "./crypto"; // @ts-ignore @@ -97,7 +97,11 @@ const keepAliveAsync = async (event: FetchEvent) => { async function generateDpopAsync(originalRequest: Request, currentDatabase:OidcConfig|null, url: string, extrasClaims={} ) { const headersExtras = serializeHeaders(originalRequest.headers); - if (currentDatabase && currentDatabase.demonstratingProofOfPossessionConfiguration && currentDatabase.demonstratingProofOfPossessionJwkJson) { + if (currentDatabase && + currentDatabase.demonstratingProofOfPossessionConfiguration && + currentDatabase.demonstratingProofOfPossessionJwkJson && + (!currentDatabase.demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent || currentDatabase.demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent && headersExtras['dpop']) + ) { const dpopConfiguration = currentDatabase.demonstratingProofOfPossessionConfiguration; const jwk = currentDatabase.demonstratingProofOfPossessionJwkJson; headersExtras['dpop'] = await generateJwtDemonstratingProofOfPossessionAsync(self)(dpopConfiguration)(jwk, 'POST', url, extrasClaims); @@ -363,6 +367,7 @@ const handleMessage = async (event: ExtendableMessageEvent) => { demonstratingProofOfPossessionNonce: null, demonstratingProofOfPossessionJwkJson: null, demonstratingProofOfPossessionConfiguration: null, + demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent: false, }; currentDatabase = database[configurationName]; @@ -379,6 +384,7 @@ const handleMessage = async (event: ExtendableMessageEvent) => { currentDatabase.demonstratingProofOfPossessionNonce = null; currentDatabase.demonstratingProofOfPossessionJwkJson = null; currentDatabase.demonstratingProofOfPossessionConfiguration = null; + currentDatabase.demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent = false; currentDatabase.status = data.data.status; port.postMessage({ configurationName }); return; @@ -398,6 +404,7 @@ const handleMessage = async (event: ExtendableMessageEvent) => { } currentDatabase.oidcServerConfiguration = oidcServerConfiguration; currentDatabase.oidcConfiguration = data.data.oidcConfiguration; + if(currentDatabase.demonstratingProofOfPossessionConfiguration == null ){ const demonstratingProofOfPossessionConfiguration = getDpopConfiguration(trustedDomains[configurationName]); @@ -407,6 +414,7 @@ const handleMessage = async (event: ExtendableMessageEvent) => { } currentDatabase.demonstratingProofOfPossessionConfiguration = demonstratingProofOfPossessionConfiguration; currentDatabase.demonstratingProofOfPossessionJwkJson = await generateJwkAsync(self)(demonstratingProofOfPossessionConfiguration.generateKeyAlgorithm); + currentDatabase.demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent = getDpopOnlyWhenDpopHeaderPresent(trustedDomains[configurationName]) ?? false; } } diff --git a/packages/oidc-client-service-worker/src/dpop.ts b/packages/oidc-client-service-worker/src/dpop.ts index dfa516d1e..921c9f9ad 100644 --- a/packages/oidc-client-service-worker/src/dpop.ts +++ b/packages/oidc-client-service-worker/src/dpop.ts @@ -19,4 +19,17 @@ export const getDpopConfiguration = (trustedDomain: Domain[] | DomainDetails) => } return trustedDomain.demonstratingProofOfPossessionConfiguration ?? defaultDemonstratingProofOfPossessionConfiguration; +} + +export const getDpopOnlyWhenDpopHeaderPresent = (trustedDomain: Domain[] | DomainDetails) => { + + if(!isDpop(trustedDomain)) { + return null; + } + + if (Array.isArray(trustedDomain)) { + return null; + } + + return trustedDomain.demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent ?? true; } \ No newline at end of file diff --git a/packages/oidc-client-service-worker/src/types.ts b/packages/oidc-client-service-worker/src/types.ts index ef3244671..dd687f4a1 100644 --- a/packages/oidc-client-service-worker/src/types.ts +++ b/packages/oidc-client-service-worker/src/types.ts @@ -6,6 +6,7 @@ export type DomainDetails = { convertAllRequestsToCorsExceptNavigate?: boolean, setAccessTokenToNavigateRequests?: boolean, demonstratingProofOfPossession?:boolean; + demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent?:boolean; demonstratingProofOfPossessionConfiguration?: DemonstratingProofOfPossessionConfiguration; } @@ -84,6 +85,7 @@ export type OidcConfig = { setAccessTokenToNavigateRequests: boolean, demonstratingProofOfPossessionNonce: string | null; demonstratingProofOfPossessionJwkJson: string | null; + demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent: boolean; } export type IdTokenPayload = { diff --git a/packages/oidc-client-service-worker/src/utils/__tests__/domains.spec.ts b/packages/oidc-client-service-worker/src/utils/__tests__/domains.spec.ts index 75aa7cf58..eea8e1a46 100644 --- a/packages/oidc-client-service-worker/src/utils/__tests__/domains.spec.ts +++ b/packages/oidc-client-service-worker/src/utils/__tests__/domains.spec.ts @@ -54,6 +54,7 @@ describe('domains', () => { demonstratingProofOfPossessionNonce: null, demonstratingProofOfPossessionJwkJson: null, demonstratingProofOfPossessionConfiguration: null, + demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent: false, }, }; }); diff --git a/packages/oidc-client-service-worker/src/utils/__tests__/testHelper.ts b/packages/oidc-client-service-worker/src/utils/__tests__/testHelper.ts index 6e30e61e2..fd960ba65 100644 --- a/packages/oidc-client-service-worker/src/utils/__tests__/testHelper.ts +++ b/packages/oidc-client-service-worker/src/utils/__tests__/testHelper.ts @@ -129,6 +129,7 @@ class OidcConfigBuilder { demonstratingProofOfPossessionNonce: null, demonstratingProofOfPossessionJwkJson: null, demonstratingProofOfPossessionConfiguration: null, + demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent: false, }; public withTestingDefault(): OidcConfigBuilder { diff --git a/packages/oidc-client/README.md b/packages/oidc-client/README.md index b4904bdfb..576aadcbc 100644 --- a/packages/oidc-client/README.md +++ b/packages/oidc-client/README.md @@ -102,7 +102,8 @@ trustedDomains.config_show_access_token = { // DPoP (Demonstrating Proof of Possession) will be activated for the following domains trustedDomains.config_with_dpop = { domains: ["https://demo.duendesoftware.com"], - demonstratingProofOfPossession: true + demonstratingProofOfPossession: true, + demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent: true, // default value is false, inject DPOP token only when DPOP header is present // Optional, more details bellow /*demonstratingProofOfPossessionConfiguration: { importKeyAlgorithm: { @@ -386,16 +387,18 @@ export class OidcClient { /** * Retrieves a new fetch function that inject bearer tokens (also DPOP tokens). * @param fetch The current fetch function to use + * @param demonstrating_proof_of_possession Indicates whether the demonstration of proof of possession should be used. * @returns Fetch A new fectch function that inject bearer tokens (also DPOP tokens). */ - fetchWithTokens(fetch: Fetch): Fetch; + fetchWithTokens(fetch: Fetch, demonstrating_proof_of_possession=false): Fetch; /** * Retrieves OIDC user information. * @param noCache Indicates whether user information should be retrieved bypassing the cache. + * @param demonstrating_proof_of_possession Indicates whether the demonstration of proof of possession should be used. * @returns A promise resolved with the user information, or rejected with an error. */ - async userInfoAsync(noCache = false): Promise; + async userInfoAsync(noCache = false, demonstrating_proof_of_possession=false): Promise; /** * Generate Demonstration of proof of possession. diff --git a/packages/oidc-client/src/fetch.ts b/packages/oidc-client/src/fetch.ts index 5d1fdc72a..1d33fc86d 100644 --- a/packages/oidc-client/src/fetch.ts +++ b/packages/oidc-client/src/fetch.ts @@ -3,7 +3,7 @@ import {OidcClient} from "./oidcClient"; import {getValidTokenAsync} from "./parseTokens"; // @ts-ignore -export const fetchWithTokens = (fetch: Fetch, oidcClient: Oidc | null) : Fetch => async (...params: Parameters) :Promise => { +export const fetchWithTokens = (fetch: Fetch, oidcClient: Oidc | null, demonstrating_proof_of_possession:boolean=false) : Fetch => async (...params: Parameters) :Promise => { const [url, options, ...rest] = params; const optionTmp = options ? { ...options } : { method: 'GET' }; let headers = new Headers(); @@ -21,7 +21,7 @@ export const fetchWithTokens = (fetch: Fetch, oidcClient: Oidc | null) : Fetch = headers.set('Accept', 'application/json'); } if (accessToken) { - if(oidc.configuration.demonstrating_proof_of_possession) { + if(oidc.configuration.demonstrating_proof_of_possession && demonstrating_proof_of_possession) { const demonstrationOdProofOfPossession = await oidc.generateDemonstrationOfProofOfPossessionAsync(accessToken, url.toString(), optionTmp.method); headers.set('Authorization', `PoP ${accessToken}`); headers.set('DPoP', demonstrationOdProofOfPossession); diff --git a/packages/oidc-client/src/oidc.ts b/packages/oidc-client/src/oidc.ts index 56f4082c5..0398a05f7 100644 --- a/packages/oidc-client/src/oidc.ts +++ b/packages/oidc-client/src/oidc.ts @@ -332,11 +332,11 @@ Please checkout that you are using OIDC hook inside a = null; - userInfoAsync(noCache = false) { + userInfoAsync(noCache = false, demonstrating_proof_of_possession=false) { if (this.userInfoPromise !== null) { return this.userInfoPromise; } - this.userInfoPromise = userInfoAsync(this)(noCache); + this.userInfoPromise = userInfoAsync(this)(noCache, demonstrating_proof_of_possession); return this.userInfoPromise.then(result => { this.userInfoPromise = null; return result; diff --git a/packages/oidc-client/src/oidcClient.ts b/packages/oidc-client/src/oidcClient.ts index 2e2762014..7a799e17a 100644 --- a/packages/oidc-client/src/oidcClient.ts +++ b/packages/oidc-client/src/oidcClient.ts @@ -75,11 +75,11 @@ export class OidcClient { return getValidTokenAsync(this._oidc, waitMs, numberWait); } - fetchWithTokens(fetch: Fetch): Fetch { - return fetchWithTokens(fetch, this); + fetchWithTokens(fetch: Fetch, demonstrating_proof_of_possession:false): Fetch { + return fetchWithTokens(fetch, this, demonstrating_proof_of_possession); } - async userInfoAsync(noCache = false):Promise { + async userInfoAsync(noCache = false, demonstrating_proof_of_possession:boolean=false):Promise { return this._oidc.userInfoAsync(noCache); } } diff --git a/packages/oidc-client/src/user.ts b/packages/oidc-client/src/user.ts index c01033d95..361b59c9b 100644 --- a/packages/oidc-client/src/user.ts +++ b/packages/oidc-client/src/user.ts @@ -1,7 +1,7 @@ import Oidc from "./oidc"; import {fetchWithTokens} from "./fetch"; -export const userInfoAsync = (oidc:Oidc) => async (noCache = false) => { +export const userInfoAsync = (oidc:Oidc) => async (noCache = false, demonstrating_proof_of_possession=false) => { if (oidc.userInfo != null && !noCache) { return oidc.userInfo; } @@ -9,7 +9,7 @@ export const userInfoAsync = (oidc:Oidc) => async (noCache = false) => { const oidcServerConfiguration = await oidc.initAsync(configuration.authority, configuration.authority_configuration); const url = oidcServerConfiguration.userInfoEndpoint; const fetchUserInfo = async () => { - const oidcFetch = fetchWithTokens(fetch, oidc); + const oidcFetch = fetchWithTokens(fetch, oidc, demonstrating_proof_of_possession); const response = await oidcFetch(url); if (response.status !== 200) { return null; diff --git a/packages/react-oidc/README.md b/packages/react-oidc/README.md index 17586e3c6..5a02047ff 100644 --- a/packages/react-oidc/README.md +++ b/packages/react-oidc/README.md @@ -101,7 +101,8 @@ trustedDomains.config_show_access_token = { // DPoP (Demonstrating Proof of Possession) will be activated for the following domains trustedDomains.config_with_dpop = { domains: ["https://demo.duendesoftware.com"], - demonstratingProofOfPossession: true + demonstratingProofOfPossession: true, + demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent: true, // default value is false, inject DPOP token only when DPOP header is present // Optional, more details bellow /*demonstratingProofOfPossessionConfiguration: { importKeyAlgorithm: { diff --git a/packages/react-oidc/src/FetchToken.tsx b/packages/react-oidc/src/FetchToken.tsx index af94a512f..e311778e0 100644 --- a/packages/react-oidc/src/FetchToken.tsx +++ b/packages/react-oidc/src/FetchToken.tsx @@ -7,27 +7,27 @@ export interface ComponentWithOidcFetchProps { const defaultConfigurationName = 'default'; -const fetchWithToken = (fetch: Fetch, getOidcWithConfigurationName: () => OidcClient | null) => async (...params: Parameters) => { +const fetchWithToken = (fetch: Fetch, getOidcWithConfigurationName: () => OidcClient | null, demonstrating_proof_of_possession=false) => async (...params: Parameters) => { const oidc = getOidcWithConfigurationName(); - const newFetch = oidc.fetchWithTokens(fetch); + const newFetch = oidc.fetchWithTokens(fetch, demonstrating_proof_of_possession); return await newFetch(...params); }; -export const withOidcFetch = (fetch: Fetch = null, configurationName = defaultConfigurationName) => ( +export const withOidcFetch = (fetch: Fetch = null, configurationName = defaultConfigurationName, demonstrating_proof_of_possession:boolean=false) => ( WrappedComponent, ) => (props: ComponentWithOidcFetchProps) => { - const { fetch: newFetch } = useOidcFetch(fetch || props.fetch, configurationName); + const { fetch: newFetch } = useOidcFetch(fetch || props.fetch, configurationName, demonstrating_proof_of_possession); return ; }; -export const useOidcFetch = (fetch: Fetch = null, configurationName = defaultConfigurationName) => { +export const useOidcFetch = (fetch: Fetch = null, configurationName = defaultConfigurationName, demonstrating_proof_of_possession:boolean=false) => { const previousFetch = fetch || window.fetch; const getOidc = OidcClient.get; const memoizedFetchCallback = useCallback( (input: RequestInfo | URL, init?: RequestInit) => { const getOidcWithConfigurationName = () => getOidc(configurationName); - const newFetch = fetchWithToken(previousFetch, getOidcWithConfigurationName); + const newFetch = fetchWithToken(previousFetch, getOidcWithConfigurationName, demonstrating_proof_of_possession); return newFetch(input, init); }, [previousFetch, configurationName], diff --git a/packages/react-oidc/src/User.ts b/packages/react-oidc/src/User.ts index fb25ad4ea..d7693f748 100644 --- a/packages/react-oidc/src/User.ts +++ b/packages/react-oidc/src/User.ts @@ -13,7 +13,7 @@ export type OidcUser = { status: OidcUserStatus; } -export const useOidcUser = (configurationName = 'default') => { +export const useOidcUser = (configurationName = 'default', demonstrating_proof_of_possession=false) => { const [oidcUser, setOidcUser] = useState>({ user: null, status: OidcUserStatus.Unauthenticated }); const [oidcUserId, setOidcUserId] = useState(''); @@ -23,7 +23,7 @@ export const useOidcUser = (configuration if (oidc && oidc.tokens) { setOidcUser({ ...oidcUser, status: OidcUserStatus.Loading }); const isNoCache = oidcUserId !== ''; - oidc.userInfoAsync(isNoCache) + oidc.userInfoAsync(isNoCache, demonstrating_proof_of_possession) .then((info) => { if (isMounted) { // @ts-ignore