apereo/cas · warning

LoggingUtils.warn(LOGGER, e)

Error message

LoggingUtils.warn(LOGGER, e)

What it means

In DuoSecurityUniversalPromptValidateLoginAction, processStateFromBrowserStorage() (invoked from handleDuoSecurityUniversalPromptResponse while validating the Duo Universal Prompt response) wraps its work in try/catch(Throwable) and only logs the failure at WARN via LoggingUtils.warn(LOGGER, e). The exception is swallowed: no error transition is returned here, and the flow continues; the 'message' value shown is the logging call itself, meaning any downstream error surfaces only in logs.

Solutions

  1. Reproduce with DEBUG/TRACE logging on org.apereo.cas.adaptors.duo and read the stack trace that LoggingUtils.warn emitted
  2. Clear browser cookies/session state and retry the Duo universal prompt flow end to end
  3. Verify the browser session store (e.g. session cookies / storage backend) is consistently configured across CAS nodes in a cluster
  4. Check for version skew between nodes that could break deserialization of stored session attributes
  5. If you need fail-closed behavior, override/extend the action to return the error transition instead of only logging

Example fix

// before
catch (final Throwable e) {
    LoggingUtils.warn(LOGGER, e);
}
// after
catch (final Throwable e) {
    LoggingUtils.error(LOGGER, e);
    return eventFactory.event(this, CasWebflowConstants.TRANSITION_ID_ERROR);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before invoking the flow action, confirm browser session store has Duo state:
var attrs = browserSessionStore.getSessionAttributes(webContext);
if (!attrs.containsKey(Credential.class.getSimpleName())) {
    // restart the Duo prompt flow instead of proceeding
}

Try / catch

try {
    return processStateFromBrowserStorage(requestContext, browserSessionStore);
} catch (Throwable e) {
    LoggingUtils.warn(LOGGER, e);
    return eventFactory.event(this, CasWebflowConstants.TRANSITION_ID_ERROR);
}

Prevention

When it happens

Trigger: Any Throwable thrown while restoring Duo state from browser session storage and populating credential/authentication/service into the webflow context: corrupt or expired browser session store entries, deserialization failures of stored attributes, null/missing state from the Duo universal prompt round-trip.

Common situations: User's browser session/cookies expired between Duo prompt and callback; server restart wiped local session store; attributes stored in browserSessionStore fail to deserialize after a CAS upgrade; misconfigured session/cas.ticket… storage so stored attributes are absent.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-duo-core/src/main/java/org/apereo/cas/adaptors/duo/web/flow/action/DuoSecurityUniversalPromptValidateLoginAction.java:127

            browserSessionStore.getSessionAttributes(webContext).forEach((key, value) -> {
                if (key.equalsIgnoreCase(FlowScope.class.getSimpleName())) {
                    populateRequestContextScope(value, requestContext.getFlowScope());
                } else if (key.equalsIgnoreCase(FlashScope.class.getSimpleName())) {
                    populateRequestContextScope(value, requestContext.getFlashScope());
                } else if (key.equalsIgnoreCase(RequestScope.class.getSimpleName())) {
                    populateRequestContextScope(value, requestContext.getRequestScope());
                } else if (key.equalsIgnoreCase(ConversationScope.class.getSimpleName())) {
                    populateRequestContextScope(value, requestContext.getConversationScope());
                } else {
                    requestContext.getFlowScope().put(key, value);
                }
            });
            populateContextWithCredential(requestContext, browserSessionStore);
            populateContextWithAuthentication(requestContext, browserSessionStore);
            populateContextWithService(requestContext, browserSessionStore);
            return super.doExecuteInternal(requestContext);
        } catch (final Throwable e) {
            LoggingUtils.warn(LOGGER, e);
        } finally {
            if (browserSessionStore != null) {
                val credential = (Credential) browserSessionStore.getSessionAttributes(webContext).get(Credential.class.getSimpleName());
                WebUtils.putCredential(requestContext, credential);
            }
        }
        return eventFactory.event(this, CasWebflowConstants.TRANSITION_ID_ERROR);
    }

    private static JEEContext toWebContext(final RequestContext requestContext) {
        val request = WebUtils.getHttpServletRequestFromExternalWebflowContext(requestContext);
        val response = WebUtils.getHttpServletResponseFromExternalWebflowContext(requestContext);
        return new JEEContext(request, response);
    }

    private static void populateRequestContextScope(final Object flowAttributes, final MutableAttributeMap<Object> requestContext) {
        val mappedAttributes = new LinkedHashMap<>((Map) flowAttributes);
        CollectionUtils.filter(mappedAttributes.values(), PredicateUtils.notNullPredicate());

View on GitHub (pinned to e7288fc434)