From b20c90fb9f074934e25040a81a2faeb87e5f4d72 Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Wed, 3 Jun 2026 18:02:20 +0200 Subject: [PATCH] feat(auth0-nuxt): forward query params as authorization params on login Pass through additional query parameters from the login route to the authorization request via authorizationParams, while filtering out returnTo, reserved OAuth parameters, and prototype-polluting keys. --- .../runtime/server/api/auth/login.get.spec.ts | 61 +++++++++++++++++++ .../src/runtime/server/api/auth/login.get.ts | 35 +++++++++++ 2 files changed, 96 insertions(+) diff --git a/packages/auth0-nuxt/src/runtime/server/api/auth/login.get.spec.ts b/packages/auth0-nuxt/src/runtime/server/api/auth/login.get.spec.ts index e22d4ba..4d307e7 100644 --- a/packages/auth0-nuxt/src/runtime/server/api/auth/login.get.spec.ts +++ b/packages/auth0-nuxt/src/runtime/server/api/auth/login.get.spec.ts @@ -50,6 +50,7 @@ describe('login.get handler', () => { expect(mockAuth0Client.startInteractiveLogin).toHaveBeenCalledWith({ appState: { returnTo: 'http://localhost:3000/foo' }, + authorizationParams: undefined, }); }); @@ -60,6 +61,66 @@ describe('login.get handler', () => { expect(mockAuth0Client.startInteractiveLogin).toHaveBeenCalledWith({ appState: { returnTo: undefined }, + authorizationParams: undefined, + }); + }); + + it('should forward additional query params as authorizationParams', async () => { + getQueryMock.mockReturnValue({ + returnTo: 'http://localhost:3000/foo', + organization: 'org_123', + login_hint: 'user@example.com', + }); + + await loginHandler(mockEvent); + + expect(mockAuth0Client.startInteractiveLogin).toHaveBeenCalledWith({ + appState: { returnTo: 'http://localhost:3000/foo' }, + authorizationParams: { + organization: 'org_123', + login_hint: 'user@example.com', + }, + }); + }); + + it('should not forward returnTo or reserved OAuth params as authorizationParams', async () => { + getQueryMock.mockReturnValue({ + returnTo: 'http://localhost:3000/foo', + scope: 'openid evil', + audience: 'https://evil', + redirect_uri: 'https://evil', + client_id: 'evil', + state: 'evil', + nonce: 'evil', + response_type: 'evil', + code_challenge: 'evil', + code_challenge_method: 'evil', + organization: 'org_123', + }); + + await loginHandler(mockEvent); + + expect(mockAuth0Client.startInteractiveLogin).toHaveBeenCalledWith({ + appState: { returnTo: 'http://localhost:3000/foo' }, + authorizationParams: { + organization: 'org_123', + }, + }); + }); + + it('should not forward prototype-polluting keys as authorizationParams', async () => { + getQueryMock.mockReturnValue({ + returnTo: 'http://localhost:3000/foo', + __proto__: 'evil', + constructor: 'evil', + prototype: 'evil', + }); + + await loginHandler(mockEvent); + + expect(mockAuth0Client.startInteractiveLogin).toHaveBeenCalledWith({ + appState: { returnTo: 'http://localhost:3000/foo' }, + authorizationParams: undefined, }); }); diff --git a/packages/auth0-nuxt/src/runtime/server/api/auth/login.get.ts b/packages/auth0-nuxt/src/runtime/server/api/auth/login.get.ts index 7bfbb53..84e124e 100644 --- a/packages/auth0-nuxt/src/runtime/server/api/auth/login.get.ts +++ b/packages/auth0-nuxt/src/runtime/server/api/auth/login.get.ts @@ -6,6 +6,40 @@ interface LoginParams { returnTo?: string; } +const DENIED_KEYS = new Set([ + ...Object.getOwnPropertyNames(Object.prototype), + '__proto__', + 'constructor', + 'prototype', +]); + +const RESERVED_OAUTH_PARAMS = new Set([ + 'response_type', + 'state', + 'code_challenge', + 'code_challenge_method', + 'client_id', + 'redirect_uri', + 'nonce', + 'scope', + 'audience', +]); + +function filterAuthorizationParams( + params: Record, + disallowed: string[] +): Record | undefined { + const filtered: Record = Object.create(null); + + for (const [key, value] of Object.entries(params)) { + if (!DENIED_KEYS.has(key) && !disallowed.includes(key) && !RESERVED_OAUTH_PARAMS.has(key)) { + filtered[key] = value; + } + } + + return Object.keys(filtered).length > 0 ? filtered : undefined; +} + export default defineEventHandler(async (event) => { const auth0Client = useAuth0(event); const auth0ClientOptions = event.context.auth0ClientOptions; @@ -16,6 +50,7 @@ export default defineEventHandler(async (event) => { const authorizationUrl = await auth0Client.startInteractiveLogin({ appState: { returnTo: sanitizedReturnTo }, + authorizationParams: filterAuthorizationParams(query, ['returnTo']), }); sendRedirect(event, authorizationUrl.href);