@@ -367,10 +367,13 @@ public void skipConsentScreenOnSecondLoginWhenRememberMeIsUsed() {
367367 assertThat (page .url ()).contains ("/client/callback?code=" );
368368 }
369369
370- private String getCodeFromCallbackCaptor () {
370+ private String getCallbackQueryString () {
371371 verify (queryStringConsumer ).accept (queryStringConsumerArgumentCaptor .capture ());
372- val queryStringValue = queryStringConsumerArgumentCaptor .getValue ();
373- return Arrays .stream (queryStringValue .split ("&" ))
372+ return queryStringConsumerArgumentCaptor .getValue ();
373+ }
374+
375+ private String getCodeFromCallbackCaptor () {
376+ return Arrays .stream (getCallbackQueryString ().split ("&" ))
374377 .filter (queryStringParam -> queryStringParam .startsWith ("code=" ))
375378 .findFirst ()
376379 .map (queryStringParam -> queryStringParam .replace ("code=" , "" ))
@@ -429,6 +432,39 @@ public void doNotSkipConsentScreenOnSecondLoginWhenRememberMeIsFalse() {
429432 // Consent screen is not skipped
430433 assertThat (page .url ()).contains ("/consent" );
431434 }
435+
436+ @ Test
437+ public void denyConsentRedirectsToClientWithAccessDeniedError () {
438+ val screenshotPathProducer =
439+ ScreenshotPathProducer .builder ()
440+ .testName ("denyConsentRedirectsToClientWithAccessDeniedError" )
441+ .build ();
442+
443+ val page = browser .newPage ();
444+
445+ page .navigate (getUriToInitiateFlow ().toString ());
446+ page .screenshot (screenshotPathProducer .screenshotOptionsForStepName ("initial-load" ));
447+
448+ page .locator ("input[name=loginEmail]" ).fill ("foo@bar.com" );
449+ page .locator ("input[name=loginPassword]" ).fill ("password" );
450+
451+ page .locator ("input[name=submit]" ).click ();
452+
453+ page .waitForLoadState ();
454+ page .screenshot (screenshotPathProducer .screenshotOptionsForStepName ("after-login-submit" ));
455+
456+ page .locator ("input[id=reject]" ).click ();
457+
458+ page .waitForLoadState ();
459+ page .screenshot (screenshotPathProducer .screenshotOptionsForStepName ("after-consent-deny" ));
460+
461+ // The browser lands on the client's callback carrying a real OAuth error instead of a code.
462+ assertThat (page .url ()).contains ("/client/callback?error=access_denied" );
463+
464+ val queryString = getCallbackQueryString ();
465+ assertThat (queryString ).contains ("error=access_denied" );
466+ assertThat (queryString ).doesNotContain ("code=" );
467+ }
432468}
433469
434470@ JsonNaming (PropertyNamingStrategies .SnakeCaseStrategy .class )
0 commit comments