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
- 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
- Generate keys with the CAS-provided key generator or 'openssl rand -base64 32' and store them in a secret manager
- 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
- Include gauth crypto keys in the deployment checklist for production
- Generate and store encryption/signing keys in a secret manager, injected via env vars
- Audit stored Gauth account records periodically for plaintext secrets
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- No federation keys defined for entity
- Interrupt webflow cookie encryption/signing is not enabled…
- Primary ticket-granting ticket expiration policy is set to…
- Ticket-granting ticket expiration policy is set to ALWAYS…
- Ticket registry encryption/signing for
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)