Skip to content

Fixing Re-authentication with passkeys#10

Open
akshayutture-augment wants to merge 2 commits into
improve-auth-user-experiencefrom
enhance-passkey-authentication-flow
Open

Fixing Re-authentication with passkeys#10
akshayutture-augment wants to merge 2 commits into
improve-auth-user-experiencefrom
enhance-passkey-authentication-flow

Conversation

@akshayutture-augment

@akshayutture-augment akshayutture-augment commented Nov 14, 2025

Copy link
Copy Markdown

Test 1

Summary by CodeRabbit

  • New Features

    • Added support for re-authentication workflows using passkeys (passwordless authentication).
    • Improved conditional passkeys handling during login forms to provide seamless authentication experiences.
  • Bug Fixes

    • Enhanced form state management during re-authentication after authentication errors, ensuring proper username field visibility and registration options.
  • Tests

    • Extended test coverage for passkeys re-authentication scenarios, including passwordless and password fallback flows.

✏️ Tip: You can customize this high-level summary in your review settings.


Replicated from ai-code-review-evaluation/keycloak-coderabbit#1

mposolda and others added 2 commits July 22, 2025 10:00
closes #41242
closes #41008

Signed-off-by: mposolda <mposolda@gmail.com>
This change modifies the method signature to require a UserModel parameter
for proper user context validation during conditional passkey checks.
@akshayutture-augment

Copy link
Copy Markdown
Author

@coderabbitai review

@akshayutture-augment

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 22, 2025

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Nov 22, 2025

Copy link
Copy Markdown

Walkthrough

The PR refactors WebAuthn passwordless authentication by centralizing reauthentication setup into a utility method, introducing conditional passkey checking across authenticators, exposing a flag for broader access, and adding comprehensive test coverage for passwordless re-authentication flows with various credential scenarios.

Changes

Cohort / File(s) Summary
Authenticator Framework Refactoring
services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
Made USER_SET_BEFORE_USERNAME_PASSWORD_AUTH flag public and refactored badPasswordHandler to use centralized AuthenticatorUtils.setupReauthenticationInUsernamePasswordFormError() for form reauthentication setup.
Conditional Passkeys Logic
services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernameForm.java, services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
Added isConditionalPasskeysEnabled() helper method to gate WebAuthn UI logic; modified authentication flow to skip form display when user is set and conditional passkeys are disabled.
WebAuthn Authenticator Enhancements
services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnAuthenticator.java, services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnConditionalUIAuthenticator.java
Added shouldShowWebAuthnAuthenticators() helper methods to control authenticator display; integrated centralized reauthentication setup in error paths.
Utility Method Addition
services/src/main/java/org/keycloak/authentication/authenticators/util/AuthenticatorUtils.java
Introduced setupReauthenticationInUsernamePasswordFormError() public static method to centralize reauthentication state configuration (username field hiding, registration disabling).
Passwordless Re-authentication Tests
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysOrganizationAuthenticationTest.java, testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysUsernameFormTest.java
Added test methods webauthnLoginWithDiscoverableKey_reauthentication() to validate re-authentication flows with passkeys under prompt=login conditions.
Comprehensive Passwordless Re-authentication Coverage
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysUsernamePasswordFormTest.java
Added extensive re-authentication test scenarios including external key re-auth, password/passkey fallback, and disabled passkey handling; introduced setPasswordlessPolicyForExternalKey() helper for test setup.

Sequence Diagrams

sequenceDiagram
    actor User
    participant LoginFlow as Login Flow
    participant UsernameForm as UsernameForm
    participant WebAuthn as WebAuthn Authenticator
    participant AuthUtils as AuthenticatorUtils
    participant Form as Form Provider

    User->>LoginFlow: Initiate Authentication
    LoginFlow->>UsernameForm: authenticate()
    
    rect rgb(220, 240, 255)
        Note over UsernameForm: Check: user set AND<br/>conditional passkeys disabled?
        alt User set + Passkeys disabled
            UsernameForm->>LoginFlow: success() + return early
        else Continue normal flow
            UsernameForm->>WebAuthn: super.authenticate()
        end
    end
    
    rect rgb(240, 220, 255)
        Note over WebAuthn: Check:<br/>shouldShowWebAuthnAuthenticators()?
        alt Show WebAuthn
            WebAuthn->>Form: Display WebAuthn authenticators
        else Skip WebAuthn
            WebAuthn->>LoginFlow: Continue to next handler
        end
    end
Loading
sequenceDiagram
    participant Handler as Bad Password Handler
    participant Context as Auth Context
    participant AuthUtils as AuthenticatorUtils
    participant Form as LoginFormsProvider

    Handler->>Context: Bad password detected
    
    rect rgb(255, 240, 220)
        Note over Handler: New: Call centralized<br/>reauthentication setup
        Handler->>AuthUtils: setupReauthenticationInUsernamePasswordFormError(context)
    end
    
    rect rgb(200, 255, 200)
        AuthUtils->>Context: getAuthNote(USER_SET_BEFORE_...)
        alt Reauthentication triggered
            AuthUtils->>Form: setHideUsername(true)
            AuthUtils->>Form: setDisableRegistration(true)
        end
    end
    
    Handler->>Form: challenge() with reauthentication state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Multiple heterogeneous changes: Each authenticator class receives different modifications (some gain helper methods, others are refactored) requiring separate reasoning for conditional logic flow and consistency.
  • Centralized utility method: AuthenticatorUtils.setupReauthenticationInUsernamePasswordFormError() refactors previously inline logic; verify correct handling of auth notes and form attribute updates.
  • Conditional passkey logic: New isConditionalPasskeysEnabled() and shouldShowWebAuthnAuthenticators() methods introduce authentication flow branching; trace through login paths to ensure correct behavior in each scenario.
  • High-complexity test file: PasskeysUsernamePasswordFormTest.java introduces extensive re-authentication scenarios with multiple test methods and new helper method; verify test isolation, credential setup, and event assertion accuracy.
  • Test duplication: PasskeysUsernameFormTest.java appears to have duplicate test method insertion requiring verification of intent and uniqueness.

Poem

🐰 A flag now public, helpers stand tall,
Passkeys dance freely through login's great hall,
Re-auth flows streamlined with utils so neat,
Tests multiplied where passwordless and prompt compete! 🔐✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is severely incomplete, containing only 'Test 1' and a replication note. It lacks any explanation of the changes, motivation, impact, or reference to linked issues. Provide a detailed description explaining the re-authentication fix, what issues it addresses, how the implementation works, and any testing performed. Reference any related issues or PRs.
Docstring Coverage ⚠️ Warning Docstring coverage is 37.04% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title directly matches the main objective of the PR: fixing re-authentication functionality with passkeys. It is concise, specific, and accurately summarizes the primary change across multiple authenticator files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch enhance-passkey-authentication-flow

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysOrganizationAuthenticationTest.java (1)

269-321: Consider extracting common setup to reduce duplication.

The test correctly validates the re-authentication flow with passkeys. However, lines 271-302 duplicate the setup and initial login from webauthnLoginWithDiscoverableKey() (lines 88-119). Consider extracting the common setup (WebAuthn configuration, user registration, and initial login verification) into a helper method to improve maintainability.

Example refactor to reduce duplication:

// Add helper method to the class
private UserRepresentation setupAndLoginWithDiscoverableKey() throws IOException {
    try (Closeable c = getWebAuthnRealmUpdater()
            .setWebAuthnPolicyRpEntityName("localhost")
            .setWebAuthnPolicyRequireResidentKey(Constants.WEBAUTHN_POLICY_OPTION_YES)
            .setWebAuthnPolicyUserVerificationRequirement(Constants.WEBAUTHN_POLICY_OPTION_REQUIRED)
            .setWebAuthnPolicyPasskeysEnabled(Boolean.TRUE)
            .update()) {
        
        checkWebAuthnConfiguration(Constants.WEBAUTHN_POLICY_OPTION_YES, Constants.WEBAUTHN_POLICY_OPTION_REQUIRED);
        registerDefaultUser();
        
        UserRepresentation user = userResource().toRepresentation();
        MatcherAssert.assertThat(user, Matchers.notNullValue());
        
        logout();
        events.clear();
        
        // Auto-login with discoverable key
        oauth.openLoginForm();
        WaitUtils.waitForPageToLoad();
        appPage.assertCurrent();
        
        events.expectLogin()
                .user(user.getId())
                .detail(Details.USERNAME, user.getUsername())
                .detail(Details.CREDENTIAL_TYPE, WebAuthnCredentialModel.TYPE_PASSWORDLESS)
                .detail(WebAuthnConstants.USER_VERIFICATION_CHECKED, "true")
                .assertEvent();
        
        return user;
    }
}

Then simplify both tests to use this helper.

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysUsernamePasswordFormTest.java (5)

26-26: Unify JUnit assertion style for readability

You import org.junit.Assert (line 26) and also statically import assertEquals and fail (lines 51-53), then use both styles (Assert.assertEquals and bare assertEquals). For consistency and easier scanning, consider standardizing on either the static imports or the class-qualified style across this test class.

Also applies to: 51-53


152-152: Simplify “element should not exist” assertions and narrow exception handling

In several places you rely on:

  • assertThat(..., nullValue()) around driver.findElement(...) inside a try { ...; fail(...) } catch (Exception e) { /* expected */ }.

Functionally it works (tests fail when the element exists and pass when a “not found” exception is thrown), but:

  • catch (Exception e) is very broad and can hide unrelated WebDriver or test infrastructure issues.
  • The nullValue() matcher is redundant because findElement will never return null; it either returns an element or throws.

Consider a more explicit and narrow pattern, for example (conceptually):

// Pseudocode-style refactor:
List<WebElement> elems = driver.findElements(By.xpath("//form[@id='webauth']"));
assertTrue("Not expected to have webauthn button", elems.isEmpty());

or, if you prefer exception-based checks, catch only the specific “element not found” exception type instead of generic Exception.

Also applies to: 291-296, 312-317


173-210: passwordLoginWithExternalKey test behavior no longer matches its name

The passwordLoginWithExternalKey test now:

  • Configures an external resident key via DefaultVirtualAuthOptions.DEFAULT_RESIDENT_KEY.
  • Sets the realm policy using setPasswordlessPolicyForExternalKey().
  • Forces login using the WebAuthn link and asserts a successful passkey-based login.

There is no password-based login path exercised here anymore; the test exclusively validates passkey authentication with an external key. Consider renaming the test (e.g., to something like passkeyLoginWithExternalKey) or adding an explicit password-login branch if the original intent was to cover both paths.


279-333: Re-authentication without passkey is well-specified; consider tightening exception scope

This test nicely covers the case where the PASSKEYS feature is enabled but the realm has passkeys disabled:

  • Realm is explicitly configured with setWebAuthnPolicyPasskeysEnabled(FALSE).
  • Both initial login and subsequent prompt=login re-auth flow assert that the WebAuthn form is not present.
  • Final login uses password-only, and the event assertion checks that USER_VERIFICATION_CHECKED is null.

The only concern is the use of catch (Exception nsee) around driver.findElement(...): this will treat any exception as “expected”, not just “element not found”, which can mask unrelated issues. Narrowing this to the specific “element not found” exception type (while letting other exceptions fail the test) would make the test more reliable.


336-422: Re-authentication with either passkey or password is robustly exercised

The webauthnLoginWithExternalKey_reauthenticationWithPasswordOrPasskey test does a good job of covering mixed-credential re-auth:

  • Initial authentication via external passkey.
  • Re-auth with prompt=login, showing both password and passkey options.
  • Failed password attempt (using another user’s password), ensuring the error is shown and passkey option remains.
  • Successful re-auth via passkey, with proper event details.
  • Second re-auth with another failed password attempt, followed by successful re-auth via the correct password, and event assertion that USER_VERIFICATION_CHECKED is null.

Two minor improvement opportunities:

  • You might additionally assert the credential type for the final password-based login (e.g., Details.CREDENTIAL_TYPE with the password credential type) for symmetry with the passkey assertion above.
  • As noted in another comment, fix the .detail(..., nullValue()) usage on USER_VERIFICATION_CHECKED to pass a plain null.

Functionally, though, the coverage and flow look solid.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30f804a and 3214b18.

📒 Files selected for processing (9)
  • services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java (2 hunks)
  • services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernameForm.java (1 hunks)
  • services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java (3 hunks)
  • services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnAuthenticator.java (2 hunks)
  • services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnConditionalUIAuthenticator.java (3 hunks)
  • services/src/main/java/org/keycloak/authentication/authenticators/util/AuthenticatorUtils.java (3 hunks)
  • testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysOrganizationAuthenticationTest.java (4 hunks)
  • testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysUsernameFormTest.java (2 hunks)
  • testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysUsernamePasswordFormTest.java (8 hunks)
🔇 Additional comments (16)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysUsernameFormTest.java (2)

37-37: LGTM! Import is required for the new re-authentication test.

The OIDCLoginProtocol import is used in the new test method to access the PROMPT_VALUE_LOGIN constant for triggering re-authentication.


259-321: Well-structured test for passkey re-authentication flow.

The test thoroughly validates that passkey authentication works during re-authentication when prompt=login is used. The implementation correctly:

  • Configures passkey-only authentication by removing the password credential
  • Verifies initial automatic login via discoverable key
  • Tests re-authentication with prompt=login parameter (line 306)
  • Confirms passkey credential type in login events

The test follows the established pattern from webauthnLoginWithDiscoverableKey() and properly handles the re-authentication scenario where no explicit logout is needed before triggering prompt=login.

Note: The AI summary mentions "the new test method appears twice in the file (duplicate insertion)," but only one instance is visible in the provided code. Please verify whether a duplicate exists elsewhere in the file.

services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java (2)

160-162: LGTM! Well-structured helper method.

The new isConditionalPasskeysEnabled helper centralizes the conditional passkey logic that guards WebAuthn UI setup. The method correctly checks all three necessary conditions: WebAuthn authenticator instance exists, passkeys feature is enabled, and user is non-null.


113-117: Improved readability with centralized conditional check.

The restructured code now uses the helper method to guard WebAuthn form setup, making the conditional logic more explicit and maintainable compared to inline checks.

services/src/main/java/org/keycloak/authentication/authenticators/util/AuthenticatorUtils.java (1)

122-134: Well-designed utility for centralized reauthentication setup.

This method effectively consolidates the logic for configuring the login form during re-authentication error scenarios. By reading the USER_SET_BEFORE_USERNAME_PASSWORD_AUTH flag and conditionally hiding the username field while disabling registration, it ensures consistent UI behavior across different authenticator error paths.

services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java (2)

58-58: Appropriate visibility change for cross-package access.

Making USER_SET_BEFORE_USERNAME_PASSWORD_AUTH public is necessary to support the centralized reauthentication setup in AuthenticatorUtils (which resides in a different package). This aligns with the broader refactoring to consolidate reauthentication UI logic.


222-222: Excellent refactor: eliminates duplication.

Delegating to AuthenticatorUtils.setupReauthenticationInUsernamePasswordFormError(context) consolidates the reauthentication form configuration logic, removing inline duplication and making the code more maintainable. This aligns with the DRY principle.

services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnAuthenticator.java (2)

124-130: Good polymorphic design for UI visibility control.

Extracting the condition into a named shouldShowWebAuthnAuthenticators method improves code clarity and enables subclasses like WebAuthnConditionalUIAuthenticator to override this behavior. The Javadoc clearly documents the typical use case (2-factor authentication scenarios).


101-101: Improved readability with intention-revealing method.

Replacing the inline user != null check with shouldShowWebAuthnAuthenticators(context) makes the code more self-documenting and aligns with the broader refactoring to centralize WebAuthn UI logic.

services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnConditionalUIAuthenticator.java (2)

52-53: Consistent error handling with centralized setup.

Invoking AuthenticatorUtils.setupReauthenticationInUsernamePasswordFormError(context) in the error response path ensures the form is properly configured for re-authentication scenarios, maintaining consistency with other authenticators in the flow.


63-67: Correct polymorphic override for conditional UI behavior.

Overriding shouldShowWebAuthnAuthenticators to return false is appropriate for the conditional passkeys flow (e.g., username/password with inline passkey support), where displaying the authenticators list would be redundant. The comment clearly documents this intent.

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysOrganizationAuthenticationTest.java (2)

35-35: LGTM!

The import is necessary for the new re-authentication test method and is correctly used at line 306.


197-211: Behavior change confirmed and correctly implemented.

The webauth form element (//form[@id='webauth']) is now conditionally rendered via passkeys.ftl when the ENABLE_WEBAUTHN_CONDITIONAL_UI attribute is set. Both the normal and error code paths in WebAuthnConditionalUIAuthenticator set this attribute, ensuring the webauth form remains visible throughout the non-discoverable key reauthentication flow, allowing users to retry with passkeys after a password error.

The implementation is correct and the test expectations at lines 200 and 211 align with the intended behavior where passkeys remain available during the username/password authentication flow.

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysUsernamePasswordFormTest.java (3)

67-75: configureTestRealm hook in addTestRealms looks appropriate

Calling configureTestRealm(realmRepresentation); before adding the realm ensures any shared/passkey-specific realm tweaks from the base test infrastructure are applied uniformly. This makes the new re-authentication scenarios more robust against future realm configuration changes.


213-275: Re-auth with external passkey flow looks correct and well-covered

The webauthnLoginWithExternalKey_reauthentication test:

  • Uses the external resident key setup and shared helper.
  • Verifies initial login via passkey, then performs a re-authentication with prompt=login.
  • Asserts the presence of the WebAuthn form, the re-auth info message, and that re-authentication via passkey succeeds with the expected event details.

The sequence and assertions match the intended behavior for passkey-based re-auth with external keys.


424-431: Helper setPasswordlessPolicyForExternalKey nicely centralizes shared setup

Extracting the common WebAuthn realm configuration into setPasswordlessPolicyForExternalKey():

  • Removes duplication across multiple tests.
  • Makes the external-key policy (resident key required, UV required, passkeys enabled) explicit in one place.
  • Preserves the try-with-resources pattern so realm changes are automatically reverted.

This is a clean and maintainable refactor.

@Override
public void authenticate(AuthenticationFlowContext context) {
if (context.getUser() != null) {
if (context.getUser() != null && !isConditionalPasskeysEnabled()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Method call missing required argument.

The method isConditionalPasskeysEnabled() is defined in the parent class UsernamePasswordForm with a required parameter UserModel user (line 160 in UsernamePasswordForm.java), but it's being called here without any arguments. This will cause a compilation error.

Apply this diff to fix the method call:

-        if (context.getUser() != null && !isConditionalPasskeysEnabled()) {
+        if (context.getUser() != null && !isConditionalPasskeysEnabled(context.getUser())) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (context.getUser() != null && !isConditionalPasskeysEnabled()) {
if (context.getUser() != null && !isConditionalPasskeysEnabled(context.getUser())) {
🤖 Prompt for AI Agents
In
services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernameForm.java
around line 47, the call to isConditionalPasskeysEnabled() is missing the
required UserModel parameter; change the call to pass the current user (e.g.,
isConditionalPasskeysEnabled(context.getUser())) so the method signature matches
the parent class and compilation succeeds.

}
}

// Test users is able to authenticate with passkey during re-authentication (for example when OIDC parameter prompt=login is used)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix grammatical error in comment.

The comment has a grammatical issue: "Test users is able" should be either "Test user is able" or "Tests that users are able".

Apply this diff to correct the grammar:

-    // Test users is able to authenticate with passkey during re-authentication (for example when OIDC parameter prompt=login is used)
+    // Tests that users are able to authenticate with passkey during re-authentication (for example when OIDC parameter prompt=login is used)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Test users is able to authenticate with passkey during re-authentication (for example when OIDC parameter prompt=login is used)
// Tests that users are able to authenticate with passkey during re-authentication (for example when OIDC parameter prompt=login is used)
🤖 Prompt for AI Agents
In
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysOrganizationAuthenticationTest.java
around line 268, the comment "Test users is able to authenticate with passkey
during re-authentication (for example when OIDC parameter prompt=login is used)"
is grammatically incorrect; update it to read "Tests that users are able to
authenticate with passkey during re-authentication (for example when OIDC
parameter prompt=login is used)" to correct subject-verb agreement and maintain
the original meaning.

.user(user.getId())
.detail(Details.USERNAME, USERNAME)
.detail(Details.CREDENTIAL_TYPE, Matchers.nullValue())
.detail(Details.CREDENTIAL_TYPE, nullValue())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Do not pass Hamcrest nullValue() into events.detail(...)

In these expectations:

  • Line 165: .detail(Details.CREDENTIAL_TYPE, nullValue())
  • Line 328: .detail(WebAuthnConstants.USER_VERIFICATION_CHECKED, nullValue())
  • Line 417: .detail(WebAuthnConstants.USER_VERIFICATION_CHECKED, nullValue())

nullValue() is a Hamcrest Matcher, but detail(...) on the events expectation API is designed to take a String (or null) value, not a matcher. This is likely to be a compile-time type error and, even if it compiled via some overload, it would not perform the intended “no value present” check.

To express “this detail should be absent / null”, pass null explicitly and reserve nullValue() for assertThat(...) calls. For example:

-                    .detail(Details.CREDENTIAL_TYPE, nullValue())
+                    .detail(Details.CREDENTIAL_TYPE, (String) null)
...
-                    .detail(WebAuthnConstants.USER_VERIFICATION_CHECKED, nullValue())
+                    .detail(WebAuthnConstants.USER_VERIFICATION_CHECKED, (String) null)

Apply the same pattern at all three call sites.

Please recompile the tests after this change to confirm there are no remaining type errors in the event expectation API.

Also applies to: 325-329, 413-418


I'll now verify this review comment by examining the file and understanding the detail() method signature.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find and examine the test file with context around the problematic lines
fd -t f "PasskeysUsernamePasswordFormTest.java" --exec cat {} \;

Length of output: 18421


🏁 Script executed:

#!/bin/bash
# Search for the detail() method definition in the codebase to understand its signature
rg -n "detail\s*\(" --type=java -B2 -A2 | head -100

Length of output: 14036


🏁 Script executed:

#!/bin/bash
# Check if nullValue() is used and where it's defined/imported
rg -n "nullValue" --type=java | grep -E "(import|nullValue\(\))"

Length of output: 50644


I found the file and confirmed the problematic code. Now I need to verify the detail() method signature to understand what it actually accepts.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find the detail() method definition - likely in AssertEvents or EventExpectation class
rg -n "detail\s*\(\s*String" --type=java -B2 -A5 | head -80

Length of output: 6693


🏁 Script executed:

#!/bin/bash
# Search for the events class/interface definition that has the detail method
fd -t f "AssertEvents.java" --exec cat {} \; | head -200

Length of output: 8404


🏁 Script executed:

#!/bin/bash
# Look for event expectation API classes
fd -t f "*Event*" --type=java | grep -i "expect\|assert" | head -20

Length of output: 171


Fix all three locations to explicitly pass (String) null instead of nullValue()

The review comment is correct. While nullValue() is a Hamcrest Matcher that will compile via the detail(String key, Matcher<? super String> matcher) overload, this is semantically wrong. The intended behavior is to express "this detail should be absent," which is achieved by passing null explicitly to the detail(String key, String value) overload. Passing a Matcher attempts to match the detail value, which has different semantics.

Lines 165, 328, and 417 all need the cast applied:

-                    .detail(Details.CREDENTIAL_TYPE, nullValue())
+                    .detail(Details.CREDENTIAL_TYPE, (String) null)
-                    .detail(WebAuthnConstants.USER_VERIFICATION_CHECKED, nullValue())
+                    .detail(WebAuthnConstants.USER_VERIFICATION_CHECKED, (String) null)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.detail(Details.CREDENTIAL_TYPE, nullValue())
.detail(Details.CREDENTIAL_TYPE, (String) null)
🤖 Prompt for AI Agents
In
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/webauthn/passwordless/PasskeysUsernamePasswordFormTest.java
around lines 165, 328 and 417 the test is incorrectly passing the Hamcrest
matcher nullValue() to detail(...) which selects the detail(String, Matcher)
overload; change each nullValue() to an explicit casted null by passing (String)
null so the detail(String, String) overload is used and the test expresses an
absent detail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants