apereo/cas · warning

Google Authenticator one-time token account…

Error message

Google Authenticator one-time token account encryption/signing is turned off. Consider turning on encryption, signing to securely and safely store one-time token accounts.

What it means

CAS builds the cipher executor used to encrypt/sign stored Google Authenticator accounts. When cas.authn.mfa.gauth.crypto.enabled is false, it falls back to CipherExecutor.noOp() and logs this warning: one-time token account records (secrets) are stored in cleartext. This is a hardening warning, not a runtime failure.

Solutions

  1. Set cas.authn.mfa.gauth.crypto.encryption.key and cas.authn.mfa.gauth.crypto.signing.key (Base64-encoded random keys) and set cas.authn.mfa.gauth.crypto.enabled=true
  2. Generate keys with the CAS-provided key generator or 'openssl rand -base64 32' and store them in a secret manager
  3. Re-encrypt existing stored accounts after enabling crypto if your storage backend supports it

Example fix

# before
cas.authn.mfa.gauth.crypto.enabled=false
# after
cas.authn.mfa.gauth.crypto.enabled=true
cas.authn.mfa.gauth.crypto.encryption.key=<base64-32-byte-key>
cas.authn.mfa.gauth.crypto.signing.key=<base64-32-byte-key>
Defensive patterns

Strategy: validation

Validate before calling

var crypto = casProperties.getAuthn().getMfa().getGauth().getCrypto();
if (!crypto.isEnabled()) {
    LOGGER.warn("Gauth account crypto disabled; enable before production");
}

Prevention

When it happens

Trigger: Bootstrapping the GoogleAuthenticatorAuthenticationEventExecutionPlanConfiguration with crypto.enabled=false, typically because no encryption/signing key was configured under cas.authn.mfa.gauth.crypto.

Common situations: Fresh CAS deployments where Gauth crypto keys were never generated; environments migrated from older defaults; teams unaware that account secrets land unencrypted in the storage backend (JSON/JDBC/Mongo).

Understand the failure class

Related errors


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

Appendix: source

Thrown at support/cas-server-support-gauth/src/main/java/org/apereo/cas/config/GoogleAuthenticatorAuthenticationEventExecutionPlanConfiguration.java:147

        @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
        @Bean
        @ConditionalOnMissingBean(name = "googleAuthenticatorInstance")
        public CasGoogleAuthenticator googleAuthenticatorInstance(
            @Qualifier(TenantExtractor.BEAN_NAME)
            final TenantExtractor tenantExtractor,
            final CasConfigurationProperties casProperties) {
            return new DefaultCasGoogleAuthenticator(casProperties, tenantExtractor);
        }

        @ConditionalOnMissingBean(name = "googleAuthenticatorAccountCipherExecutor")
        @Bean
        @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
        public CipherExecutor googleAuthenticatorAccountCipherExecutor(final CasConfigurationProperties casProperties) {
            val crypto = casProperties.getAuthn().getMfa().getGauth().getCrypto();
            if (crypto.isEnabled()) {
                return CipherExecutorUtils.newStringCipherExecutor(crypto, OneTimeTokenAccountCipherExecutor.class);
            }
            LOGGER.warn("Google Authenticator one-time token account encryption/signing is turned off. "
                + "Consider turning on encryption, signing to securely and safely store one-time token accounts.");
            return CipherExecutor.noOp();
        }

        @ConditionalOnMissingBean(name = "googleAuthenticatorScratchCodesCipherExecutor")
        @Bean
        @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
        public CipherExecutor googleAuthenticatorScratchCodesCipherExecutor(final ApplicationContext applicationContext,
                                                                            final CasConfigurationProperties casProperties) {
            return BeanSupplier.of(CipherExecutor.class)
                .when(CONDITION_SCRATCH_CODE.given(applicationContext.getEnvironment()))
                .supply(() -> {
                    val key = casProperties.getAuthn().getMfa().getGauth().getCore().getScratchCodes().getEncryption().getKey();
                    return new JasyptNumberCipherExecutor(key, "googleAuthenticatorScratchCodesCipherExecutor");
                })
                .otherwise(() -> {
                    LOGGER.warn("Google Authenticator scratch codes encryption key is not defined. "
                        + "Consider defining the encryption key to securely and safely store scratch codes.");

View on GitHub (pinned to e7288fc434)