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

  1. 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.
  2. 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.
  3. 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

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


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 {
        @Bean

View on GitHub (pinned to e7288fc434)