diff --git a/client/src/js/modules/oidcProvider.js b/client/src/js/modules/oidcProvider.js index 334690fd..23cbf03b 100644 --- a/client/src/js/modules/oidcProvider.js +++ b/client/src/js/modules/oidcProvider.js @@ -20,11 +20,28 @@ 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) - let beforeTime = new Date().getTime() - const tokens = await requestToken(getTokenRequestBody(params.code, redirectUrl)) - let clientTime = (beforeTime + new Date().getTime()) / 2 + + 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 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) return tokens @@ -182,24 +199,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 +229,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 +242,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() { 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 += `

` } }