@@ -337,6 +337,38 @@ describe('OAuth2Adapter', () => {
337337 ) ;
338338 } ) ;
339339
340+ it ( 'should send the correct access token to the introspection endpoint during app ID validation' , async ( ) => {
341+ const capturedTokens = [ ] ;
342+ const originalFetch = global . fetch ;
343+ global . fetch = async ( url , options ) => {
344+ if ( typeof url === 'string' && url === 'https://provider.com/introspect' ) {
345+ const body = options ?. body ?. toString ( ) || '' ;
346+ const token = new URLSearchParams ( body ) . get ( 'token' ) ;
347+ capturedTokens . push ( token ) ;
348+ return {
349+ ok : true ,
350+ json : ( ) => Promise . resolve ( {
351+ active : true ,
352+ sub : 'user123' ,
353+ aud : 'valid-app-id' ,
354+ } ) ,
355+ } ;
356+ }
357+ return originalFetch ( url , options ) ;
358+ } ;
359+
360+ const authData = { access_token : 'myRealAccessToken' , id : 'user123' } ;
361+ const user = await Parse . User . logInWith ( 'mockOauth' , { authData } ) ;
362+ expect ( user . id ) . toBeDefined ( ) ;
363+
364+ // With appidField configured, validateAppId and validateAuthData both call requestTokenInfo.
365+ // Both should receive the actual access token, not 'undefined' from argument mismatch.
366+ expect ( capturedTokens . length ) . toBeGreaterThanOrEqual ( 2 ) ;
367+ for ( const token of capturedTokens ) {
368+ expect ( token ) . toBe ( 'myRealAccessToken' ) ;
369+ }
370+ } ) ;
371+
340372 it ( 'should reject account takeover when useridField is omitted and attacker uses their own token with victim ID' , async ( ) => {
341373 await reconfigureServer ( {
342374 auth : {
0 commit comments