From 4492691ce24b76a9b428dd4e16e89505df8e3301 Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Thu, 6 Nov 2025 12:50:04 -0400 Subject: [PATCH 1/6] fix: update handlePkceVerifier to accept onlyConsume option for prevent code_challenge missing in authorize query --- src/runtime/server/lib/oauth/azureb2c.ts | 2 +- src/runtime/server/lib/oauth/kick.ts | 2 +- src/runtime/server/lib/oauth/zitadel.ts | 2 +- src/runtime/server/lib/utils.ts | 7 ++++++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/runtime/server/lib/oauth/azureb2c.ts b/src/runtime/server/lib/oauth/azureb2c.ts index 71da9e28..03795c1b 100644 --- a/src/runtime/server/lib/oauth/azureb2c.ts +++ b/src/runtime/server/lib/oauth/azureb2c.ts @@ -81,7 +81,7 @@ export function defineOAuthAzureB2CEventHandler({ config, onSuccess, onError }: config.scope = [...new Set(config.scope)] // Create pkce verifier - const verifier = await handlePkceVerifier(event) + const verifier = await handlePkceVerifier(event, { onlyConsume: !!query.code }) const state = await handleState(event) if (!query.code) { diff --git a/src/runtime/server/lib/oauth/kick.ts b/src/runtime/server/lib/oauth/kick.ts index 016efa56..901c5ac2 100644 --- a/src/runtime/server/lib/oauth/kick.ts +++ b/src/runtime/server/lib/oauth/kick.ts @@ -61,7 +61,7 @@ export function defineOAuthKickEventHandler({ config, onSuccess, onError }: OAut } // Create pkce verifier - const verifier = await handlePkceVerifier(event) + const verifier = await handlePkceVerifier(event, { onlyConsume: !!query.code }) const redirectURL = config.redirectURL || getOAuthRedirectURL(event) diff --git a/src/runtime/server/lib/oauth/zitadel.ts b/src/runtime/server/lib/oauth/zitadel.ts index c2a85993..8fc353ea 100644 --- a/src/runtime/server/lib/oauth/zitadel.ts +++ b/src/runtime/server/lib/oauth/zitadel.ts @@ -70,7 +70,7 @@ export function defineOAuthZitadelEventHandler({ config, onSuccess, onError }: O const redirectURL = config.redirectURL || getOAuthRedirectURL(event) // Create pkce verifier - const verifier = await handlePkceVerifier(event) + const verifier = await handlePkceVerifier(event, { onlyConsume: !!query.code }) const state = await handleState(event) if (!query.code) { diff --git a/src/runtime/server/lib/utils.ts b/src/runtime/server/lib/utils.ts index 699eda29..59ce6be7 100644 --- a/src/runtime/server/lib/utils.ts +++ b/src/runtime/server/lib/utils.ts @@ -181,10 +181,15 @@ function getRandomBytes(size: number = 32) { return getRandomValues(new Uint8Array(size)) } -export async function handlePkceVerifier(event: H3Event) { +export async function handlePkceVerifier( + event: H3Event, + { onlyConsume }: { onlyConsume?: boolean } = {}, +) { let verifier = getCookie(event, 'nuxt-auth-pkce') if (verifier) { deleteCookie(event, 'nuxt-auth-pkce') + } + if (onlyConsume) { return { code_verifier: verifier } } From bf546d8304110e21ddac0c3d7ce92bc7c4627110 Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Tue, 25 Nov 2025 09:13:43 -0400 Subject: [PATCH 2/6] fix: refactor handlePkceVerifier and handleState to accept onlyConsume option --- src/runtime/server/lib/oauth/azureb2c.ts | 5 +++-- src/runtime/server/lib/oauth/kick.ts | 3 ++- src/runtime/server/lib/oauth/zitadel.ts | 5 +++-- src/runtime/server/lib/utils.ts | 4 +++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/runtime/server/lib/oauth/azureb2c.ts b/src/runtime/server/lib/oauth/azureb2c.ts index 03795c1b..314d2754 100644 --- a/src/runtime/server/lib/oauth/azureb2c.ts +++ b/src/runtime/server/lib/oauth/azureb2c.ts @@ -81,8 +81,9 @@ export function defineOAuthAzureB2CEventHandler({ config, onSuccess, onError }: config.scope = [...new Set(config.scope)] // Create pkce verifier - const verifier = await handlePkceVerifier(event, { onlyConsume: !!query.code }) - const state = await handleState(event) + const onlyConsume = !!query.code + const verifier = await handlePkceVerifier(event, { onlyConsume }) + const state = await handleState(event, { onlyConsume }) if (!query.code) { // Redirect to Azure B2C Oauth page diff --git a/src/runtime/server/lib/oauth/kick.ts b/src/runtime/server/lib/oauth/kick.ts index 901c5ac2..19d894d0 100644 --- a/src/runtime/server/lib/oauth/kick.ts +++ b/src/runtime/server/lib/oauth/kick.ts @@ -61,7 +61,8 @@ export function defineOAuthKickEventHandler({ config, onSuccess, onError }: OAut } // Create pkce verifier - const verifier = await handlePkceVerifier(event, { onlyConsume: !!query.code }) + const onlyConsume = !!query.code + const verifier = await handlePkceVerifier(event, { onlyConsume }) const redirectURL = config.redirectURL || getOAuthRedirectURL(event) diff --git a/src/runtime/server/lib/oauth/zitadel.ts b/src/runtime/server/lib/oauth/zitadel.ts index 8fc353ea..e8219921 100644 --- a/src/runtime/server/lib/oauth/zitadel.ts +++ b/src/runtime/server/lib/oauth/zitadel.ts @@ -70,8 +70,9 @@ export function defineOAuthZitadelEventHandler({ config, onSuccess, onError }: O const redirectURL = config.redirectURL || getOAuthRedirectURL(event) // Create pkce verifier - const verifier = await handlePkceVerifier(event, { onlyConsume: !!query.code }) - const state = await handleState(event) + const onlyConsume = !!query.code + const verifier = await handlePkceVerifier(event, { onlyConsume }) + const state = await handleState(event, { onlyConsume }) if (!query.code) { config.scope = config.scope || ['openid'] diff --git a/src/runtime/server/lib/utils.ts b/src/runtime/server/lib/utils.ts index 59ce6be7..8777c702 100644 --- a/src/runtime/server/lib/utils.ts +++ b/src/runtime/server/lib/utils.ts @@ -209,10 +209,12 @@ export async function handlePkceVerifier( } } -export async function handleState(event: H3Event) { +export async function handleState(event: H3Event, { onlyConsume }: { onlyConsume?: boolean } = {}) { let state = getCookie(event, 'nuxt-auth-state') if (state) { deleteCookie(event, 'nuxt-auth-state') + } + if (onlyConsume) { return state } From 67ab8fbff90d90263ccf86404afb897ca57484b7 Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Tue, 2 Dec 2025 08:10:19 -0400 Subject: [PATCH 3/6] fix: remove onlyConsume option from handlePkceVerifier and handleState functions, and get the code directly from event query --- src/runtime/server/lib/oauth/azureb2c.ts | 5 ++--- src/runtime/server/lib/oauth/kick.ts | 3 +-- src/runtime/server/lib/oauth/zitadel.ts | 5 ++--- src/runtime/server/lib/utils.ts | 15 +++++++-------- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/runtime/server/lib/oauth/azureb2c.ts b/src/runtime/server/lib/oauth/azureb2c.ts index 314d2754..71da9e28 100644 --- a/src/runtime/server/lib/oauth/azureb2c.ts +++ b/src/runtime/server/lib/oauth/azureb2c.ts @@ -81,9 +81,8 @@ export function defineOAuthAzureB2CEventHandler({ config, onSuccess, onError }: config.scope = [...new Set(config.scope)] // Create pkce verifier - const onlyConsume = !!query.code - const verifier = await handlePkceVerifier(event, { onlyConsume }) - const state = await handleState(event, { onlyConsume }) + const verifier = await handlePkceVerifier(event) + const state = await handleState(event) if (!query.code) { // Redirect to Azure B2C Oauth page diff --git a/src/runtime/server/lib/oauth/kick.ts b/src/runtime/server/lib/oauth/kick.ts index 19d894d0..016efa56 100644 --- a/src/runtime/server/lib/oauth/kick.ts +++ b/src/runtime/server/lib/oauth/kick.ts @@ -61,8 +61,7 @@ export function defineOAuthKickEventHandler({ config, onSuccess, onError }: OAut } // Create pkce verifier - const onlyConsume = !!query.code - const verifier = await handlePkceVerifier(event, { onlyConsume }) + const verifier = await handlePkceVerifier(event) const redirectURL = config.redirectURL || getOAuthRedirectURL(event) diff --git a/src/runtime/server/lib/oauth/zitadel.ts b/src/runtime/server/lib/oauth/zitadel.ts index e8219921..c2a85993 100644 --- a/src/runtime/server/lib/oauth/zitadel.ts +++ b/src/runtime/server/lib/oauth/zitadel.ts @@ -70,9 +70,8 @@ export function defineOAuthZitadelEventHandler({ config, onSuccess, onError }: O const redirectURL = config.redirectURL || getOAuthRedirectURL(event) // Create pkce verifier - const onlyConsume = !!query.code - const verifier = await handlePkceVerifier(event, { onlyConsume }) - const state = await handleState(event, { onlyConsume }) + const verifier = await handlePkceVerifier(event) + const state = await handleState(event) if (!query.code) { config.scope = config.scope || ['openid'] diff --git a/src/runtime/server/lib/utils.ts b/src/runtime/server/lib/utils.ts index 8777c702..29769089 100644 --- a/src/runtime/server/lib/utils.ts +++ b/src/runtime/server/lib/utils.ts @@ -1,4 +1,4 @@ -import { type H3Event, deleteCookie, getCookie, setCookie } from 'h3' +import { type H3Event, deleteCookie, getCookie, getQuery, setCookie } from 'h3' import { getRequestURL } from 'h3' import { FetchError } from 'ofetch' import { snakeCase, upperFirst } from 'scule' @@ -181,15 +181,13 @@ function getRandomBytes(size: number = 32) { return getRandomValues(new Uint8Array(size)) } -export async function handlePkceVerifier( - event: H3Event, - { onlyConsume }: { onlyConsume?: boolean } = {}, -) { +export async function handlePkceVerifier(event: H3Event) { let verifier = getCookie(event, 'nuxt-auth-pkce') if (verifier) { deleteCookie(event, 'nuxt-auth-pkce') } - if (onlyConsume) { + const query = getQuery<{ code?: string }>(event) + if (query.code) { return { code_verifier: verifier } } @@ -209,12 +207,13 @@ export async function handlePkceVerifier( } } -export async function handleState(event: H3Event, { onlyConsume }: { onlyConsume?: boolean } = {}) { +export async function handleState(event: H3Event) { let state = getCookie(event, 'nuxt-auth-state') if (state) { deleteCookie(event, 'nuxt-auth-state') } - if (onlyConsume) { + const query = getQuery<{ code?: string }>(event) + if (query.code) { return state } From 26aa9301b288f97f5a15a598c3c4e5705a522c64 Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Tue, 2 Dec 2025 08:14:19 -0400 Subject: [PATCH 4/6] fix: add verifier and state checks in handlePkceVerifier and handleState functions --- src/runtime/server/lib/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/server/lib/utils.ts b/src/runtime/server/lib/utils.ts index 29769089..3a781e21 100644 --- a/src/runtime/server/lib/utils.ts +++ b/src/runtime/server/lib/utils.ts @@ -187,7 +187,7 @@ export async function handlePkceVerifier(event: H3Event) { deleteCookie(event, 'nuxt-auth-pkce') } const query = getQuery<{ code?: string }>(event) - if (query.code) { + if (verifier && query.code) { return { code_verifier: verifier } } @@ -213,7 +213,7 @@ export async function handleState(event: H3Event) { deleteCookie(event, 'nuxt-auth-state') } const query = getQuery<{ code?: string }>(event) - if (query.code) { + if (state && query.code) { return state } From 0a899e899367a3882c0da631e68372cf17c4bd06 Mon Sep 17 00:00:00 2001 From: Jeffrey Leon Date: Tue, 2 Dec 2025 08:16:28 -0400 Subject: [PATCH 5/6] fix: ensure verifier and state checks only proceed if query code is present --- src/runtime/server/lib/utils.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/runtime/server/lib/utils.ts b/src/runtime/server/lib/utils.ts index 3a781e21..84cd533f 100644 --- a/src/runtime/server/lib/utils.ts +++ b/src/runtime/server/lib/utils.ts @@ -185,10 +185,11 @@ export async function handlePkceVerifier(event: H3Event) { let verifier = getCookie(event, 'nuxt-auth-pkce') if (verifier) { deleteCookie(event, 'nuxt-auth-pkce') - } - const query = getQuery<{ code?: string }>(event) - if (verifier && query.code) { - return { code_verifier: verifier } + + const query = getQuery<{ code?: string }>(event) + if (query.code) { + return { code_verifier: verifier } + } } // Create new verifier @@ -211,10 +212,11 @@ export async function handleState(event: H3Event) { let state = getCookie(event, 'nuxt-auth-state') if (state) { deleteCookie(event, 'nuxt-auth-state') - } - const query = getQuery<{ code?: string }>(event) - if (state && query.code) { - return state + + const query = getQuery<{ code?: string }>(event) + if (query.code) { + return state + } } state = encodeBase64Url(getRandomBytes(8)) From 705d798d95701e775ef61759ca95bd83ecb3594d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Tue, 9 Dec 2025 14:49:56 +0100 Subject: [PATCH 6/6] chore: update logic --- src/runtime/server/lib/oauth/azureb2c.ts | 1 - src/runtime/server/lib/utils.ts | 45 +++++++++++------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/runtime/server/lib/oauth/azureb2c.ts b/src/runtime/server/lib/oauth/azureb2c.ts index 71da9e28..f873d689 100644 --- a/src/runtime/server/lib/oauth/azureb2c.ts +++ b/src/runtime/server/lib/oauth/azureb2c.ts @@ -105,7 +105,6 @@ export function defineOAuthAzureB2CEventHandler({ config, onSuccess, onError }: return handleInvalidState(event, 'azureb2c', onError) } - console.info('code verifier', verifier.code_verifier) const tokens = await requestAccessToken(tokenURL, { body: { grant_type: 'authorization_code', diff --git a/src/runtime/server/lib/utils.ts b/src/runtime/server/lib/utils.ts index fec5cbce..c9cf4471 100644 --- a/src/runtime/server/lib/utils.ts +++ b/src/runtime/server/lib/utils.ts @@ -182,30 +182,29 @@ function getRandomBytes(size: number = 32) { } export async function handlePkceVerifier(event: H3Event) { - let verifier = getCookie(event, 'nuxt-auth-pkce') - if (verifier) { - deleteCookie(event, 'nuxt-auth-pkce') + const query = getQuery<{ code?: string }>(event) - const query = getQuery<{ code?: string }>(event) - if (query.code) { - return { code_verifier: verifier } + // Create new verifier + if (!query.code) { + const verifier = encodeBase64Url(getRandomBytes()) + setCookie(event, 'nuxt-auth-pkce', verifier) + + // Get pkce + const encodedPkce = new TextEncoder().encode(verifier) + const pkceHash = await subtle.digest('SHA-256', encodedPkce) + const pkce = encodeBase64Url(new Uint8Array(pkceHash)) + + return { + code_verifier: verifier, + code_challenge: pkce, + code_challenge_method: 'S256', } } + // If the verifier is in the cookie, get it from the cookie and delete the cookie + const verifier = getCookie(event, 'nuxt-auth-pkce') + deleteCookie(event, 'nuxt-auth-pkce') - // Create new verifier - verifier = encodeBase64Url(getRandomBytes()) - setCookie(event, 'nuxt-auth-pkce', verifier) - - // Get pkce - const encodedPkce = new TextEncoder().encode(verifier) - const pkceHash = await subtle.digest('SHA-256', encodedPkce) - const pkce = encodeBase64Url(new Uint8Array(pkceHash)) - - return { - code_verifier: verifier, - code_challenge: pkce, - code_challenge_method: 'S256', - } + return { code_verifier: verifier } } export async function handleState(event: H3Event) { @@ -214,11 +213,7 @@ export async function handleState(event: H3Event) { if (query.state) { const state = getCookie(event, 'nuxt-auth-state') deleteCookie(event, 'nuxt-auth-state') - - const query = getQuery<{ code?: string }>(event) - if (query.code) { - return state - } + return state } // If the state is not in the query, generate a new state and set it in the cookie