@@ -152,14 +152,14 @@ public function testStateDoesNotMatch(): void
152152 Config::set ('oidc.client_secret ' , 'the-secret-client-secret ' );
153153
154154 // Mock LoginResponseHandlerInterface to check handleExceptionWhileAuthenticate is called.
155- $ mock = Mockery::mock (ExceptionHandlerInterface::class);
156- $ mock
155+ $ mockExceptionHandler = Mockery::mock (ExceptionHandlerInterface::class);
156+ $ mockExceptionHandler
157157 ->shouldReceive ('handleExceptionWhileAuthenticate ' )
158158 ->withArgs (function (OpenIDConnectClientException $ e ) {
159159 return $ e ->getMessage () === 'Unable to determine state ' ;
160160 })
161161 ->once ();
162- $ this ->app ->instance (ExceptionHandlerInterface::class, $ mock );
162+ $ this ->app ->instance (ExceptionHandlerInterface::class, $ mockExceptionHandler );
163163
164164 // Set the current state, which is usually generated and saved in the session before login,
165165 // and sent to the issuer during the login redirect.
@@ -278,14 +278,14 @@ public function testIdTokenSignedWithIncorrectClientSecret(): void
278278 Config::set ('oidc.client_secret ' , 'the-secret-client-secret ' );
279279
280280 // Mock LoginResponseHandlerInterface to check handleExceptionWhileAuthenticate is called.
281- $ mock = Mockery::mock (ExceptionHandlerInterface::class);
282- $ mock
281+ $ mockExceptionHandler = Mockery::mock (ExceptionHandlerInterface::class);
282+ $ mockExceptionHandler
283283 ->shouldReceive ('handleExceptionWhileAuthenticate ' )
284284 ->withArgs (function (OpenIDConnectClientException $ e ) {
285285 return $ e ->getMessage () === 'Unable to verify signature ' ;
286286 })
287287 ->once ();
288- $ this ->app ->instance (ExceptionHandlerInterface::class, $ mock );
288+ $ this ->app ->instance (ExceptionHandlerInterface::class, $ mockExceptionHandler );
289289
290290 // Set the current state, which is usually generated and saved in the session before login,
291291 // and sent to the issuer during the login redirect.
@@ -413,6 +413,105 @@ public function testIdTokenAndUserinfoSignedWithClientSecret(): void
413413 });
414414 }
415415
416+ public function testSubClaimIdTokenDoesNotEqualsSubClaimUserinfo (): void
417+ {
418+ $ idToken = generateJwt ([
419+ "iss " => "https://provider.rdobeheer.nl " ,
420+ "aud " => 'test-client-id ' ,
421+ "sub " => 'test-subject ' ,
422+ ], 'the-secret-client-secret ' );
423+
424+ $ signedUserInfo = generateJwt ([
425+ "iss " => "https://provider.rdobeheer.nl " ,
426+ "aud " => 'test-client-id ' ,
427+ "sub " => 'different-subject ' ,
428+ "email " => 'tester@rdobeheer.nl ' ,
429+ ], 'the-secret-client-secret ' );
430+
431+ Http::fake ([
432+ // Token requested by OpenIDConnectClient::authenticate() function.
433+ 'https://provider.rdobeheer.nl/token ' => Http::response ([
434+ 'access_token ' => 'access-token-from-token-endpoint ' ,
435+ 'id_token ' => $ idToken ,
436+ 'token_type ' => 'Bearer ' ,
437+ 'expires_in ' => 3600 ,
438+ ]),
439+ // User info requested by OpenIDConnectClient::requestUserInfo() function.
440+ 'https://provider.rdobeheer.nl/userinfo?schema=openid ' => Http::response (
441+ body: $ signedUserInfo ,
442+ status: 200 ,
443+ headers: [
444+ 'Content-Type ' => 'application/jwt ' ,
445+ ]
446+ ),
447+ ]);
448+
449+ // Set OIDC config
450+ $ this ->mockOpenIDConfigurationLoader ();
451+
452+ Config::set ('oidc.issuer ' , 'https://provider.rdobeheer.nl ' );
453+ Config::set ('oidc.client_id ' , 'test-client-id ' );
454+ Config::set ('oidc.client_secret ' , 'the-secret-client-secret ' );
455+
456+ // Mock LoginResponseHandlerInterface to check handleExceptionWhileRequestUserInfo is called.
457+ $ mockExceptionHandler = Mockery::mock (ExceptionHandlerInterface::class);
458+ $ mockExceptionHandler
459+ ->shouldReceive ('handleExceptionWhileRequestUserInfo ' )
460+ ->withArgs (function (OpenIDConnectClientException $ e ) {
461+ return $ e ->getMessage () === 'Invalid JWT signature ' ;
462+ })
463+ ->once ();
464+ $ this ->app ->instance (ExceptionHandlerInterface::class, $ mockExceptionHandler );
465+
466+ // Set the current state, which is usually generated and saved in the session before login,
467+ // and sent to the issuer during the login redirect.
468+ Session::put ('openid_connect_state ' , 'some-state ' );
469+
470+ // We simulate here that the user now comes back after successful login at issuer.
471+ $ this ->getRoute ('oidc.login ' , ['code ' => 'some-code ' , 'state ' => 'some-state ' ]);
472+
473+ $ this ->assertEmpty (session ('openid_connect_state ' ));
474+ $ this ->assertEmpty (session ('openid_connect_nonce ' ));
475+
476+ Http::assertSentCount (2 );
477+ Http::assertSentInOrder ([
478+ 'https://provider.rdobeheer.nl/token ' ,
479+ 'https://provider.rdobeheer.nl/userinfo?schema=openid ' ,
480+ ]);
481+ Http::assertSent (function (Request $ request ) {
482+ if ($ request ->url () === 'https://provider.rdobeheer.nl/token ' ) {
483+ $ this ->assertSame (
484+ expected: 'POST ' ,
485+ actual: $ request ->method (),
486+ );
487+ $ this ->assertSame (
488+ expected: 'grant_type=authorization_code '
489+ . '&code=some-code '
490+ . '&redirect_uri=http%3A%2F%2Flocalhost%2Foidc%2Flogin '
491+ . '&client_id=test-client-id '
492+ . '&client_secret=the-secret-client-secret ' ,
493+ actual: $ request ->body (),
494+ );
495+ return true ;
496+ }
497+
498+ if ($ request ->url () === 'https://provider.rdobeheer.nl/userinfo?schema=openid ' ) {
499+ $ this ->assertSame (
500+ expected: 'GET ' ,
501+ actual: $ request ->method (),
502+ );
503+ $ this ->assertSame (
504+ expected: [
505+ 'Bearer access-token-from-token-endpoint '
506+ ],
507+ actual: $ request ->header ('Authorization ' ),
508+ );
509+ }
510+
511+ return true ;
512+ });
513+ }
514+
416515 public function testTokenSignedWithPrivateKey (): void
417516 {
418517 Http::fake ([
0 commit comments