From c499f8445a0ede248587f54b25bb635aed956411 Mon Sep 17 00:00:00 2001 From: Thomas Diesler Date: Wed, 8 Apr 2026 15:55:22 +0200 Subject: [PATCH] [OID4VCI] Revisit invalid authorization requests handling Signed-off-by: Thomas Diesler --- .../tests/oid4vc/OID4VCBasicWallet.java | 22 ++++++++++++++++-- .../tests/oid4vc/OID4VCPublicClientTest.java | 10 ++++++-- ...ID4VCAuthorizationDetailsFlowTestBase.java | 8 +++---- .../testsuite/util/oauth/LoginUrlBuilder.java | 23 +++++++++++++++++++ 4 files changed, 54 insertions(+), 9 deletions(-) diff --git a/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCBasicWallet.java b/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCBasicWallet.java index 2ff8f8fddcd2..a6cf74e2baa0 100644 --- a/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCBasicWallet.java +++ b/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCBasicWallet.java @@ -550,12 +550,30 @@ public AuthorizationEndpointRequest scope(String... scopes) { return this; } - public void openLoginForm() { + public boolean openLoginForm() { loginForm.open(); + String currUrl = oauth.getDriver().getCurrentUrl(); + return currUrl != null && !currUrl.contains("error=") && !currUrl.contains("error_description="); + } + + public AuthorizationEndpointRequest fillLoginForm(String username, String password) { + oauth.fillLoginForm(username, password); + return this; + } + + public AuthorizationEndpointResponse parseLoginResponse() { + return oauth.parseLoginResponse(); } public AuthorizationEndpointResponse send(String username, String password) { - return loginForm.doLogin(username, password); + openLoginForm(); + fillLoginForm(username, password); + return parseLoginResponse(); + } + + public AuthorizationEndpointResponse send() { + openLoginForm(); + return parseLoginResponse(); } } } diff --git a/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCPublicClientTest.java b/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCPublicClientTest.java index f5b6889011c6..9eaa6d32d733 100644 --- a/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCPublicClientTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCPublicClientTest.java @@ -27,6 +27,7 @@ import org.keycloak.protocol.oid4vc.model.VerifiableCredential; import org.keycloak.representations.JsonWebToken; import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.tests.oid4vc.OID4VCBasicWallet.AuthorizationEndpointRequest; import org.keycloak.tests.oid4vc.OID4VCIssuerTestBase.VCTestServerConfig; import org.keycloak.testsuite.util.oauth.AccessTokenResponse; import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse; @@ -39,6 +40,7 @@ import static org.keycloak.OID4VCConstants.OPENID_CREDENTIAL; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -124,8 +126,12 @@ public void testAuthorizationRequestNoPkce() { // Send AuthorizationRequest without required PKCE // - oauth.loginForm().scope(ctx.getScope()).open(); - AuthorizationEndpointResponse authResponse = oauth.parseLoginResponse(); + AuthorizationEndpointRequest authRequest = wallet + .authorizationRequest() + .scope(ctx.getScope()); + + assertFalse(authRequest.openLoginForm(), "Error expected"); + AuthorizationEndpointResponse authResponse = authRequest.parseLoginResponse(); assertNull(authResponse.getCode(), "Expected no auth code"); assertEquals("invalid_request", authResponse.getError()); diff --git a/tests/base/src/test/java/org/keycloak/tests/oid4vc/issuance/signing/OID4VCAuthorizationDetailsFlowTestBase.java b/tests/base/src/test/java/org/keycloak/tests/oid4vc/issuance/signing/OID4VCAuthorizationDetailsFlowTestBase.java index 1864c43550e9..d7c5e513a45d 100644 --- a/tests/base/src/test/java/org/keycloak/tests/oid4vc/issuance/signing/OID4VCAuthorizationDetailsFlowTestBase.java +++ b/tests/base/src/test/java/org/keycloak/tests/oid4vc/issuance/signing/OID4VCAuthorizationDetailsFlowTestBase.java @@ -192,12 +192,10 @@ private void runAuthorizationDetailsTest( try { AuthorizationEndpointRequest authRequest = authRequestSupplier.get(); - authRequest.openLoginForm(); - String currUrl = oauth.getDriver().getCurrentUrl(); - if (currUrl != null && !currUrl.contains("error=") && !currUrl.contains("error_description=")) { - oauth.fillLoginForm(ctx.getHolder(), TEST_PASSWORD); + if (authRequest.openLoginForm()) { + authRequest.fillLoginForm(ctx.getHolder(), TEST_PASSWORD); } - AuthorizationEndpointResponse authResponse = oauth.parseLoginResponse(); + AuthorizationEndpointResponse authResponse = authRequest.parseLoginResponse(); if (authResponse.getError() != null) throw new IllegalStateException(authResponse.getErrorDescription()); diff --git a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/LoginUrlBuilder.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/LoginUrlBuilder.java index e769e581e711..02aa50cc9414 100644 --- a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/LoginUrlBuilder.java +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/LoginUrlBuilder.java @@ -130,6 +130,29 @@ protected void initRequest() { } } + /** + * Composite login method for the Authorization Code Flow + * + *
    + *
  1. It builds and opens the authorization request url
  2. + *
  3. Fills the login form with user credentials (i.e. username, password)
  4. + *
  5. Parses the authorization response
  6. + *
+ * + * This method is intended to be used only for the purpose of the basic login flow when the server is expected to open a login form. + * + * For more complex scenarios like: + * + * + * calls to level API will be needed. + * + * In short, the caller should always know whether they expect a login-form to be shown or not. + * For details, there is this discussion. + */ public AuthorizationEndpointResponse doLogin(String username, String password) { open(); client.fillLoginForm(username, password);