diff --git a/src/core/services/AuthenticationOrchestrator.ts b/src/core/services/AuthenticationOrchestrator.ts index 2376142f4..c3e240fe6 100644 --- a/src/core/services/AuthenticationOrchestrator.ts +++ b/src/core/services/AuthenticationOrchestrator.ts @@ -190,13 +190,19 @@ export class AuthenticationOrchestrator implements IAuthenticationProvider { async refreshToken(parameters: RefreshTokenParameters): Promise { validateParameters(parameters, ['refreshToken']); - const { headers, ...payload } = parameters; + // Forward any extra parameters to the token endpoint. `RefreshTokenParameters` + // accepts arbitrary keys (via RequestOptions), and custom parameters are commonly + // consumed by Auth0 Actions to customize the issued tokens. Extras are spread + // first so they can never override the OAuth-mandated fields. + const { headers, refreshToken, scope, audience, ...extraParameters } = + parameters; const body = { + ...extraParameters, grant_type: 'refresh_token', client_id: this.clientId, - refresh_token: payload.refreshToken, - scope: includeRequiredScope(payload.scope), - audience: payload.audience, + refresh_token: refreshToken, + scope: includeRequiredScope(scope), + audience: audience, }; const { json, response } = await this.client.post( diff --git a/src/core/services/__tests__/AuthenticationOrchestrator.spec.ts b/src/core/services/__tests__/AuthenticationOrchestrator.spec.ts index 12be2f237..f9bf68a0a 100644 --- a/src/core/services/__tests__/AuthenticationOrchestrator.spec.ts +++ b/src/core/services/__tests__/AuthenticationOrchestrator.spec.ts @@ -299,6 +299,48 @@ describe('AuthenticationOrchestrator', () => { undefined ); }); + + it('should forward extra parameters to the token endpoint', async () => { + mockHttpClientInstance.post.mockResolvedValueOnce({ + json: tokensResponse, + response: new Response(null, { status: 200 }), + }); + await orchestrator.refreshToken({ + ...parameters, + organizationId: 'org_123', + }); + + expect(mockHttpClientInstance.post).toHaveBeenCalledWith( + '/oauth/token', + expect.objectContaining({ + grant_type: 'refresh_token', + refresh_token: parameters.refreshToken, + organizationId: 'org_123', + }), + undefined + ); + }); + + it('should not let extra parameters override the OAuth fields', async () => { + mockHttpClientInstance.post.mockResolvedValueOnce({ + json: tokensResponse, + response: new Response(null, { status: 200 }), + }); + await orchestrator.refreshToken({ + ...parameters, + grant_type: 'password', + client_id: 'spoofed-client', + }); + + expect(mockHttpClientInstance.post).toHaveBeenCalledWith( + '/oauth/token', + expect.objectContaining({ + grant_type: 'refresh_token', + client_id: clientId, + }), + undefined + ); + }); }); describe('revoke token', () => {