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
- Ensure the user completes the GAuth account registration (scan QR / scratch codes) which persists the account via the credential repository
- Check the configured cas.authn.mfa.gauth.credential-repository backend (json file path, db, mongo...) actually contains a record for the uid
- Verify all CAS nodes share the same credential repository storage so accounts registered on one node are visible to others
- 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
- Force users through the GAuth registration flow before OTP login
- Share the credential repository storage across all CAS nodes
- Verify the credential-repository backend config points at the environment that holds registrations
- Monitor repository.save failures that silently drop registrations
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
- Failed to authenticate code
- cannot reuse OTP
- Failed to authenticate code
- Failed to authenticate code
- Failed to authenticate code
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)