From b9538554c8faf59e740e670fa9c92609896755e6 Mon Sep 17 00:00:00 2001 From: csmig Date: Mon, 23 Dec 2024 08:04:57 -0500 Subject: [PATCH 1/4] feat: test oidc state before token request --- client/src/js/modules/oidcProvider.js | 42 +++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/client/src/js/modules/oidcProvider.js b/client/src/js/modules/oidcProvider.js index 334690fd..3aebc9d4 100644 --- a/client/src/js/modules/oidcProvider.js +++ b/client/src/js/modules/oidcProvider.js @@ -22,9 +22,15 @@ async function authorize({clientId, oidcProvider, scope, autoRefresh}) { // exchange authorization_code for token const [redirectUrl, paramStr] = window.location.href.split('#') const params = processRedirectParams(paramStr) - let beforeTime = new Date().getTime() - const tokens = await requestToken(getTokenRequestBody(params.code, redirectUrl)) - let clientTime = (beforeTime + new Date().getTime()) / 2 + + const lastOidc = JSON.parse(localStorage.getItem('last-oidc') ?? '{}') + if (lastOidc.state !== params.state) { + throw new Error(`ERROR: OIDC redirection from unknown state.
Expected: ${lastOidc.state}
Actual: ${params.state}

Retry authorization.`) + } + + const beforeTime = new Date().getTime() + const tokens = await requestToken(getTokenRequestBody(params.code, lastOidc.pkce.codeVerifier, redirectUrl)) + const clientTime = (beforeTime + new Date().getTime()) / 2 setTokens(tokens, clientTime) window.history.replaceState(window.history.state, '', redirectUrl) return tokens @@ -182,24 +188,14 @@ async function requestRefresh() { return response.json() } -function getTokenRequestBody(code, redirectUri) { - if (!localStorage.getItem('oidc-code-verifier')) { - // Will assume this function was called while handling a replayed request, - // perhaps from a bookmarked URL of an earlier authorization request. - // Try to restart authorization from the beginning by redirecting to our entry point. - window.location.href = redirectUri - throw new Error('Redirecting after not finding code verifier') - } +function getTokenRequestBody(code, codeVerifier, redirectUri) { const params = new URLSearchParams() params.append('code', code) params.append('grant_type', 'authorization_code') params.append('client_id', state.clientId) params.append('redirect_uri', redirectUri) - params.append('code_verifier', localStorage.getItem('oidc-code-verifier')) - - // Clear saved code verifier to prevent replay error scenarios - localStorage.removeItem('oidc-code-verifier') - + params.append('code_verifier', codeVerifier) + return params } @@ -222,11 +218,11 @@ function processRedirectParams(paramStr) { async function getAuthorizationUrl() { const pkce = await getPkce() - + const oidcState = crypto.randomUUID() const params = new URLSearchParams() params.append('client_id', state.clientId) params.append('redirect_uri', window.location.href) - params.append('state', crypto.randomUUID()) + params.append('state', oidcState) params.append('response_mode', 'fragment') params.append('response_type', 'code') params.append('scope', state.scope) @@ -235,11 +231,15 @@ async function getAuthorizationUrl() { params.append('code_challenge_method', 'S256') const authEndpoint = state.oidcConfiguration.authorization_endpoint + const authRequest = `${authEndpoint}?${params.toString()}` - // Save the code verifier for use after the OP redirect back to us - localStorage.setItem('oidc-code-verifier', pkce.codeVerifier) + localStorage.setItem('last-oidc', JSON.stringify({ + state: oidcState, + pkce, + authRequest + })) - return `${authEndpoint}?${params.toString()}` + return authRequest } async function getPkce() { From 50ff84def0c84e6db00d2fae19021bebfce5c355 Mon Sep 17 00:00:00 2001 From: csmig Date: Mon, 23 Dec 2024 10:24:55 -0500 Subject: [PATCH 2/4] detailed error display on token failure --- client/src/js/modules/oidcProvider.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/client/src/js/modules/oidcProvider.js b/client/src/js/modules/oidcProvider.js index 3aebc9d4..aa193cbe 100644 --- a/client/src/js/modules/oidcProvider.js +++ b/client/src/js/modules/oidcProvider.js @@ -20,16 +20,27 @@ async function authorize({clientId, oidcProvider, scope, autoRefresh}) { } else { // exchange authorization_code for token + const lastOidc = JSON.parse(localStorage.getItem('last-oidc') ?? '{}') + lastOidc.redirectHref = window.location.href const [redirectUrl, paramStr] = window.location.href.split('#') const params = processRedirectParams(paramStr) - const lastOidc = JSON.parse(localStorage.getItem('last-oidc') ?? '{}') if (lastOidc.state !== params.state) { throw new Error(`ERROR: OIDC redirection from unknown state.
Expected: ${lastOidc.state}
Actual: ${params.state}

Retry authorization.`) } const beforeTime = new Date().getTime() - const tokens = await requestToken(getTokenRequestBody(params.code, lastOidc.pkce.codeVerifier, redirectUrl)) + const tokenRequestBody = getTokenRequestBody(params.code, lastOidc.pkce.codeVerifier, redirectUrl) + let tokens + try { + lastOidc.tokenEndpoint = state.oidcConfiguration.token_endpoint + lastOidc.tokenRequestBody = tokenRequestBody.toString() + tokens = await requestToken(tokenRequestBody) + } + catch (e) { + e.message = `

Retry authorization.` + throw e + } const clientTime = (beforeTime + new Date().getTime()) / 2 setTokens(tokens, clientTime) window.history.replaceState(window.history.state, '', redirectUrl) From c30d56efe9044d8b2e1d24fa2d029dfaa261f731 Mon Sep 17 00:00:00 2001 From: csmig Date: Mon, 23 Dec 2024 13:01:22 -0500 Subject: [PATCH 3/4] textarea styling: wrap off, readonly --- client/src/js/stigman.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/stigman.js b/client/src/js/stigman.js index c0382f48..86181bfe 100644 --- a/client/src/js/stigman.js +++ b/client/src/js/stigman.js @@ -48,7 +48,7 @@ async function start () { } } catch (e) { - el.innerHTML += `

` + el.innerHTML += `

` } } From 74697c3b0d0ad47e491613cb2d7af39fe4e29832 Mon Sep 17 00:00:00 2001 From: csmig Date: Fri, 27 Dec 2024 12:46:26 -0500 Subject: [PATCH 4/4] other file for that last commit --- client/src/js/modules/oidcProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/modules/oidcProvider.js b/client/src/js/modules/oidcProvider.js index aa193cbe..23cbf03b 100644 --- a/client/src/js/modules/oidcProvider.js +++ b/client/src/js/modules/oidcProvider.js @@ -38,7 +38,7 @@ async function authorize({clientId, oidcProvider, scope, autoRefresh}) { tokens = await requestToken(tokenRequestBody) } catch (e) { - e.message = `

Retry authorization.` + e.message = `

Retry authorization.` throw e } const clientTime = (beforeTime + new Date().getTime()) / 2