apereo/cas · warning
Storing trusted device records in runtime memory. Changes…
Error message
Storing trusted device records in runtime memory. Changes and records will be lost upon CAS restarts
What it means
CAS logs this warning when no persistent storage location is configured for trusted MFA device records, so it falls back to InMemoryMultifactorAuthenticationTrustStorage. Device trust records (which devices the user chose to 'remember' for MFA) live only in runtime memory and are wiped on every CAS restart, forcing users to re-authenticate with MFA.
Solutions
- Set cas.authn.mfa.trusted.json.location=/etc/cas/config/trusted-devices.json (and ensure the path is writable and on persistent storage) so JsonMultifactorAuthenticationTrustStorage is used instead.
- If running multiple CAS nodes or containers, use a database/redis-backed trusted MFA storage module (e.g. cas-server-support-trusted-mfa-mongo/jdbc/redis) and configure its location/connection instead of JSON.
- If in-memory storage is intentional for a test environment, silence/ignore the warning, but do not use it in production.
Example fix
// before (application.properties) cas.authn.mfa.trusted.enabled=true // after cas.authn.mfa.trusted.enabled=true cas.authn.mfa.trusted.json.location=/etc/cas/config/mfa-trusted-devices.json
Defensive patterns
Strategy: validation
Validate before calling
// at startup, before enabling trusted MFA in prod
String loc = casProperties.getAuthn().getMfa().getTrusted().getJson().getLocation();
if (loc == null || loc.isBlank()) {
throw new IllegalStateException("cas.authn.mfa.trusted.json.location must be set for persistent trusted-device storage");
} Prevention
- Always set cas.authn.mfa.trusted.json.location (or a database/redis-backed variant) in production config.
- Store the trust file on persistent volume in containerized deployments.
- Grep startup logs for 'Storing trusted device records in runtime memory' as a deployment checklist item.
When it happens
Trigger: The mfaTrustEngine bean in MultifactorAuthnTrustConfiguration builds its trust storage; the fallback branch executes when casProperties.getAuthn().getMfa().getTrusted().getJson().getLocation() is null, i.e. no JSON storage file is configured (and no other persistent storage variant module is wired).
Common situations: Deployers enable CAS MFA trusted-device support but never set cas.authn.mfa.trusted.json.location; single-node dev/test environments where in-memory loss seems acceptable; Kubernetes/container deployments where pods restart frequently and trust records silently vanish.
Understand the failure class
Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.
Related errors
- Not all requested multifactor providers could be found…
- List of candidate multifactor authentication providers is…
- MultifactorAuthenticationProviderAbsentException
- ACR [ ] is not defined as a supported ACR in CAS…
- Device registration is disabled for
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/2f32abf4ad5d1fd1.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-trusted-mfa/src/main/java/org/apereo/cas/config/MultifactorAuthnTrustConfiguration.java:105
public MultifactorAuthenticationTrustStorage mfaTrustEngine(
final CasConfigurationProperties casProperties,
@Qualifier("mfaTrustCipherExecutor")
final CipherExecutor mfaTrustCipherExecutor,
@Qualifier("mfaTrustRecordKeyGenerator")
final MultifactorAuthenticationTrustRecordKeyGenerator mfaTrustRecordKeyGenerator) {
val trusted = casProperties.getAuthn().getMfa().getTrusted();
val storage = Caffeine.newBuilder().initialCapacity(INITIAL_CACHE_SIZE)
.maximumSize(MAX_CACHE_SIZE).expireAfter(new MultifactorAuthenticationTrustRecordExpiry()).build(s -> {
LOGGER.error("Load operation of the cache is not supported.");
return null;
});
return FunctionUtils.doIf(trusted.getJson().getLocation() != null, () -> {
LOGGER.debug("Storing trusted device records inside the JSON resource [{}]", trusted.getJson().getLocation());
return new JsonMultifactorAuthenticationTrustStorage(casProperties.getAuthn().getMfa().getTrusted(),
mfaTrustCipherExecutor, trusted.getJson().getLocation(),
mfaTrustRecordKeyGenerator);
}, () -> {
LOGGER.warn("Storing trusted device records in runtime memory. Changes and records will be lost upon CAS restarts");
return new InMemoryMultifactorAuthenticationTrustStorage(
casProperties.getAuthn().getMfa().getTrusted(),
mfaTrustCipherExecutor, storage, mfaTrustRecordKeyGenerator);
}).get();
}
@ConditionalOnMissingBean(name = "transactionManagerMfaAuthnTrust")
@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public PlatformTransactionManager transactionManagerMfaAuthnTrust() {
return new PseudoTransactionManager();
}
}
@Configuration(value = "MultifactorAuthnTrustCryptoConfiguration", proxyBeanMethods = false)
@EnableConfigurationProperties(CasConfigurationProperties.class)
static class MultifactorAuthnTrustCryptoConfiguration {
@BeanView on GitHub (pinned to e7288fc434)