apereo/cas · error · AccountNotFoundException

cannot be found in the registry

Error message

 cannot be found in the registry

What it means

During OTP validation, GoogleAuthenticatorOneTimeTokenCredentialValidator looks up the principal's registered accounts in the IGoogleAuthenticatorTokenCredentialRepository; when the repository returns null or an empty collection it throws AccountNotFoundException with '<uid> cannot be found in the registry'. This means no Google Authenticator account has been registered/created for that user yet.

Solutions

  1. Ensure the user completes the GAuth account registration (scan QR / scratch codes) which persists the account via the credential repository
  2. Check the configured cas.authn.mfa.gauth.credential-repository backend (json file path, db, mongo...) actually contains a record for the uid
  3. Verify all CAS nodes share the same credential repository storage so accounts registered on one node are visible to others
  4. If the record exists but is stale, re-register the account or import it via the repository API (save(account))

Example fix

// before: submitting OTP with no registration
// POST otp for uid 'jsmith' -> AccountNotFoundException: jsmith cannot be found in the registry
// after: register first
GoogleAuthenticatorAccount account = GoogleAuthenticatorAccount.builder()
    .username("jsmith").secretKey("BASE32SECRET").validationCode(123456).build();
credentialRepository.save(account, "jsmith");
Defensive patterns

Strategy: validation

Validate before calling

// ensure account exists before validating OTP
val accounts = credentialRepository.get(uid);
if (accounts == null || accounts.isEmpty()) {
    throw new AccountNotFoundException(uid + " is not enrolled; complete GAuth registration first");
}

Try / catch

try {
    validator.validate(tokenCredential, authentication);
} catch (AccountNotFoundException e) {
    return enrollmentRequired(e.getMessage());
}

Prevention

When it happens

Trigger: validator.validate() calls credentialRepository.get(uid) before verifying the OTP; if the user never registered a device (no record created via repository.save/registerAccount) or the repository backend (json, mongo, redis, jdbc) has no entry for the uid, the exception is thrown.

Common situations: User skips the GAuth registration flow and tries to submit an OTP directly; the account registry file/backend was wiped or points at a different storage (environment mismatch); registry save failed silently in a prior step; multiple CAS nodes with non-shared credential repositories.

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/7f7d8f16a0c52bd8. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-gauth-core/src/main/java/org/apereo/cas/gauth/credential/GoogleAuthenticatorOneTimeTokenCredentialValidator.java:57

        return credential.getAccountId() == null || credential.getAccountId() == account.getId();
    }

    @Override
    public @Nullable GoogleAuthenticatorToken validate(final Authentication authentication,
                                                       final GoogleAuthenticatorTokenCredential tokenCredential) throws Throwable {

        if (!StringUtils.isNumeric(tokenCredential.getToken())) {
            throw new PreventedException("Invalid non-numeric OTP format specified.");
        }

        val uid = authentication.getPrincipal().getId();
        val otp = Integer.parseInt(tokenCredential.getToken());
        LOGGER.trace("Received OTP [{}] assigned to account [{}]", otp, tokenCredential.getAccountId());

        LOGGER.trace("Received principal id [{}]. Attempting to locate account in credential repository...", uid);
        val accounts = credentialRepository.get(uid);
        if (accounts == null || accounts.isEmpty()) {
            throw new AccountNotFoundException(uid + " cannot be found in the registry");
        }

        if (accounts.size() > 1 && tokenCredential.getAccountId() == null) {
            throw new PreventedException("Account identifier must be specified if multiple accounts are registered for " + uid);
        }
        LOGGER.trace("Attempting to locate OTP token [{}] in token repository for [{}]...", otp, uid);
        if (tokenRepository.exists(uid, otp)) {
            throw new AccountExpiredException(uid + " cannot reuse OTP " + otp + " as it may be expired/invalid");
        }

        LOGGER.debug("Attempting to authorize OTP token [{}]...", otp);
        val result = getAuthorizedAccountForToken(tokenCredential, accounts)
            .or(() -> getAuthorizedScratchCodeForToken(tokenCredential, authentication, accounts));
        return result
            .map(acct -> new GoogleAuthenticatorToken(otp, uid))
            .orElse(null);
    }

View on GitHub (pinned to e7288fc434)