apereo/cas · warning

Unable to locate user based on the given user handle

Error message

Unable to locate user based on the given user handle

What it means

After resolving the WebAuthn session, the action asks webAuthnCredentialRepository.getUsernameForUserHandle(userHandle) for the account tied to the WebAuthn user handle; when empty, the user record is unknown to the credential repository and the flow fails with authentication failure. The device's user handle maps to no known user.

Solutions

  1. Re-register the device: delete the stale registration and have the user enroll WebAuthn again so a valid user-handle/credential record exists in the repository.
  2. Check the configured WebAuthn credential repository backend and verify the user handle exists there (query its storage directly); migrate data if you switched backends.
  3. On clustered deployments, ensure all nodes share the same credential repository storage so getUsernameForUserHandle resolves consistently.

Example fix

// before (records lost after switching to in-memory repo)
cas.authn.mfa.web-authn.core.username-attribute-name=
# no persistent registration storage

// after: persist registrations so user handles resolve
cas.authn.mfa.web-authn.json.location=/etc/cas/config/webauthn-accounts.json
Defensive patterns

Strategy: validation

Validate before calling

// before the ceremony, verify the user handle resolves in the credential repository
Optional<String> username = webAuthnCredentialRepository.getUsernameForUserHandle(userHandle);
if (username.isEmpty()) {
    // force re-registration of the WebAuthn device
}

Try / catch

try {
    result = webAuthnCredentialRepository.getUsernameForUserHandle(session.get());
} catch (Exception e) {
    // repository backend unreachable: fall back to re-registration prompt
}

Prevention

When it happens

Trigger: doExecuteInternal calls webAuthnCredentialRepository.getUsernameForUserHandle(session.get()) and the returned Optional is empty — the user handle in the session has no matching entry in the configured WebAuthn credential repository (in-memory, JSON, database, LDAP, etc.).

Common situations: WebAuthn registration records deleted or purged while a session/token still references them; user re-registered on a different backend so the old user handle no longer resolves; switched credential repository backends (e.g. in-memory to JDBC) losing prior registrations; multi-node setup with non-shared credential storage.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/7481c284b57a0801. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-webauthn-core-webflow/src/main/java/org/apereo/cas/webauthn/web/flow/WebAuthnValidateSessionCredentialTokenAction.java:60

        val request = WebUtils.getHttpServletRequestFromExternalWebflowContext(requestContext);
        val token = request.getParameter("token");
        if (StringUtils.isBlank(token)) {
            LOGGER.warn("Missing web authn token from the request");
            return eventFactory.event(this, CasWebflowConstants.TRANSITION_ID_AUTHENTICATION_FAILURE);
        }

        LOGGER.debug("Received web authn token [{}]", token);
        val credential = new WebAuthnCredential(token);
        WebUtils.putCredential(requestContext, credential);

        val session = sessionManager.getSession(request, WebAuthnCredential.from(credential));
        if (session.isEmpty()) {
            LOGGER.warn("Unable to locate existing session from the current token [{}]", token);
            return eventFactory.event(this, CasWebflowConstants.TRANSITION_ID_AUTHENTICATION_FAILURE);
        }
        val result = webAuthnCredentialRepository.getUsernameForUserHandle(session.get());
        if (result.isEmpty()) {
            LOGGER.warn("Unable to locate user based on the given user handle");
            return eventFactory.event(this, CasWebflowConstants.TRANSITION_ID_AUTHENTICATION_FAILURE);
        }
        val username = result.get();
        return FunctionUtils.doUnchecked(() -> {
            val authentication = DefaultAuthenticationBuilder.newInstance()
                .addCredential(credential)
                .setPrincipal(principalFactory.createPrincipal(username))
                .build();
            LOGGER.debug("Finalized authentication attempt based on [{}]", authentication);
            WebUtils.putAuthentication(authentication, requestContext);
            return eventFactory.event(this, CasWebflowConstants.TRANSITION_ID_FINALIZE);
        });
    }
}

View on GitHub (pinned to e7288fc434)