@@ -152,14 +152,14 @@ public function testStateDoesNotMatch(): void
152
152
Config::set ('oidc.client_secret ' , 'the-secret-client-secret ' );
153
153
154
154
// Mock LoginResponseHandlerInterface to check handleExceptionWhileAuthenticate is called.
155
- $ mock = Mockery::mock (ExceptionHandlerInterface::class);
156
- $ mock
155
+ $ mockExceptionHandler = Mockery::mock (ExceptionHandlerInterface::class);
156
+ $ mockExceptionHandler
157
157
->shouldReceive ('handleExceptionWhileAuthenticate ' )
158
158
->withArgs (function (OpenIDConnectClientException $ e ) {
159
159
return $ e ->getMessage () === 'Unable to determine state ' ;
160
160
})
161
161
->once ();
162
- $ this ->app ->instance (ExceptionHandlerInterface::class, $ mock );
162
+ $ this ->app ->instance (ExceptionHandlerInterface::class, $ mockExceptionHandler );
163
163
164
164
// Set the current state, which is usually generated and saved in the session before login,
165
165
// and sent to the issuer during the login redirect.
@@ -278,14 +278,14 @@ public function testIdTokenSignedWithIncorrectClientSecret(): void
278
278
Config::set ('oidc.client_secret ' , 'the-secret-client-secret ' );
279
279
280
280
// Mock LoginResponseHandlerInterface to check handleExceptionWhileAuthenticate is called.
281
- $ mock = Mockery::mock (ExceptionHandlerInterface::class);
282
- $ mock
281
+ $ mockExceptionHandler = Mockery::mock (ExceptionHandlerInterface::class);
282
+ $ mockExceptionHandler
283
283
->shouldReceive ('handleExceptionWhileAuthenticate ' )
284
284
->withArgs (function (OpenIDConnectClientException $ e ) {
285
285
return $ e ->getMessage () === 'Unable to verify signature ' ;
286
286
})
287
287
->once ();
288
- $ this ->app ->instance (ExceptionHandlerInterface::class, $ mock );
288
+ $ this ->app ->instance (ExceptionHandlerInterface::class, $ mockExceptionHandler );
289
289
290
290
// Set the current state, which is usually generated and saved in the session before login,
291
291
// and sent to the issuer during the login redirect.
@@ -413,6 +413,105 @@ public function testIdTokenAndUserinfoSignedWithClientSecret(): void
413
413
});
414
414
}
415
415
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
+
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
+
416
515
public function testTokenSignedWithPrivateKey (): void
417
516
{
418
517
Http::fake ([
0 commit comments