Core API for multifactor authentication webflow configuration in Apereo CAS providing interfaces and base classes for MFA provider integration
—
Action classes that execute during webflow state transitions to perform MFA-specific operations like availability checks, bypassing, device management, and failure handling. Actions bridge the gap between webflow states and MFA provider implementations.
Base generic action class providing common functionality for MFA actions including provider resolution and principal resolution.
/**
* Abstract base class for MFA actions providing provider resolution and principal resolution hooks
* @param <T> Type of MultifactorAuthenticationProvider this action works with
*/
public abstract class AbstractMultifactorAuthenticationAction<T extends MultifactorAuthenticationProvider>
extends BaseCasWebflowAction {
/**
* The resolved provider for this flow (available to subclasses)
*/
protected T provider;
/**
* Pre-execution hook that resolves the MFA provider for this flow
* @param requestContext The webflow request context
* @return Event result of pre-execution (typically null to continue)
* @throws Exception If provider resolution fails
*/
@Override
protected Event doPreExecute(RequestContext requestContext) throws Exception;
/**
* Resolve principal using available MultifactorAuthenticationPrincipalResolver instances
* @param principal The principal to resolve
* @param requestContext The webflow request context
* @return Resolved principal
*/
protected Principal resolvePrincipal(Principal principal, RequestContext requestContext);
/**
* Get throttled request key for authentication throttling
* @param authentication Current authentication
* @param requestContext The webflow request context
* @return Throttling key (typically principal ID)
*/
protected String getThrottledRequestKeyFor(Authentication authentication, RequestContext requestContext);
}Usage Example:
public class MyMfaVerificationAction extends AbstractMultifactorAuthenticationAction<MyMfaProvider> {
@Override
protected Event doExecuteInternal(RequestContext requestContext) throws Exception {
// Provider is automatically resolved and available as this.provider
val authentication = WebUtils.getAuthentication(requestContext);
val principal = resolvePrincipal(authentication.getPrincipal(), requestContext);
// Get MFA token from request
val token = requestContext.getRequestParameters().get("token");
// Verify token using the resolved provider
if (provider.verify(principal, token)) {
return success();
} else {
return error();
}
}
}Determines if an MFA provider is available to provide authentication.
/**
* Action that determines if MFA provider is available for authentication
*/
public class MultifactorAuthenticationAvailableAction
extends AbstractMultifactorAuthenticationAction<MultifactorAuthenticationProvider> {
/**
* Default constructor - uses inherited constructor from AbstractMultifactorAuthenticationAction
*/
public MultifactorAuthenticationAvailableAction();
/**
* Execute availability check for the MFA provider
* @param requestContext The webflow request context
* @return Event indicating availability (yes/no)
* @throws Exception If availability check fails
*/
@Override
protected Event doExecuteInternal(RequestContext requestContext) throws Exception;
}Determines if MFA provider can be bypassed for the current user and service.
/**
* Action that determines if MFA provider can be bypassed
*/
public class MultifactorAuthenticationBypassAction
extends AbstractMultifactorAuthenticationAction<MultifactorAuthenticationProvider> {
/**
* Default constructor - uses inherited constructor from AbstractMultifactorAuthenticationAction
*/
public MultifactorAuthenticationBypassAction();
/**
* Execute bypass check for the MFA provider
* @param requestContext The webflow request context
* @return Event indicating bypass decision (yes/no)
* @throws Exception If bypass check fails
*/
@Override
protected Event doExecuteInternal(RequestContext requestContext) throws Exception;
/**
* Check if multifactor authentication should be bypassed
* @param requestContext The webflow request context
* @param registeredService The service being accessed
* @param provider The MFA provider
* @return true if MFA should be bypassed
*/
protected boolean isMultifactorAuthenticationBypass(
RequestContext requestContext,
RegisteredService registeredService,
MultifactorAuthenticationProvider provider);
}Handles MFA provider failure scenarios and determines appropriate failure mode.
/**
* Action that handles MFA provider failure scenarios
*/
public class MultifactorAuthenticationFailureAction
extends AbstractMultifactorAuthenticationAction<MultifactorAuthenticationProvider> {
/**
* Default constructor - uses inherited constructor from AbstractMultifactorAuthenticationAction
*/
public MultifactorAuthenticationFailureAction();
/**
* Execute failure handling for the MFA provider
* @param requestContext The webflow request context
* @return Event indicating failure mode (unavailable/bypass)
* @throws Exception If failure handling fails
*/
@Override
protected Event doExecuteInternal(RequestContext requestContext) throws Exception;
}Functional interface for actions that provide MFA device information.
/**
* Functional interface for actions that provide MFA device information
*/
@FunctionalInterface
public interface MultifactorAuthenticationDeviceProviderAction extends Action, Ordered, NamedObject {
/**
* Get execution order for this action
* @return Order value (default LOWEST_PRECEDENCE)
*/
@Override
default int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}Functional interface for actions that handle trusted device information in MFA flows.
/**
* Functional interface for actions that handle trusted device information in MFA flows
*/
@FunctionalInterface
public interface MultifactorAuthenticationTrustedDeviceProviderAction extends Action, Ordered, NamedObject {
/**
* Get execution order for this action
* @return Order value (default LOWEST_PRECEDENCE)
*/
@Override
default int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}Default implementation for loading registered MFA devices for a user.
/**
* Default implementation for loading registered MFA devices for a user
*/
public class DefaultMultifactorAuthenticationDeviceProviderAction
implements MultifactorAuthenticationDeviceProviderAction {
/**
* Constructor
* @param deviceManager MFA device manager for loading devices
*/
public DefaultMultifactorAuthenticationDeviceProviderAction(
MultifactorAuthenticationDeviceManager deviceManager);
/**
* Execute device loading action
* @param context The action execution context
* @return Event result of device loading
* @throws Exception If device loading fails
*/
@Override
public Event execute(RequestContext context) throws Exception;
/**
* Internal execution logic for device loading
* @param requestContext The webflow request context
* @return Event result
* @throws Exception If execution fails
*/
protected Event doExecuteInternal(RequestContext requestContext) throws Exception;
}Complete Action Implementation Example:
@Component
public class CustomMfaActions {
@Bean
public Action customMfaAvailabilityAction(ApplicationEventPublisher eventPublisher) {
return new CustomMfaAvailabilityAction(eventPublisher);
}
@Bean
public MultifactorAuthenticationDeviceProviderAction customDeviceAction(
MultifactorAuthenticationDeviceManager deviceManager) {
return new CustomDeviceProviderAction(deviceManager);
}
private static class CustomMfaAvailabilityAction extends MultifactorAuthenticationAvailableAction {
public CustomMfaAvailabilityAction(ApplicationEventPublisher eventPublisher) {
super(eventPublisher);
}
@Override
protected Event doExecuteInternal(RequestContext requestContext) throws Exception {
// this.provider is automatically resolved
val principal = WebUtils.getAuthentication(requestContext).getPrincipal();
// Custom availability logic
if (isProviderAvailableForUser(principal)) {
return new EventFactorySupport().event(this, CasWebflowConstants.TRANSITION_ID_YES);
}
return new EventFactorySupport().event(this, CasWebflowConstants.TRANSITION_ID_NO);
}
private boolean isProviderAvailableForUser(Principal principal) {
// Custom logic to check if provider is available for this user
return true;
}
}
private static class CustomDeviceProviderAction implements MultifactorAuthenticationDeviceProviderAction {
private final MultifactorAuthenticationDeviceManager deviceManager;
public CustomDeviceProviderAction(MultifactorAuthenticationDeviceManager deviceManager) {
this.deviceManager = deviceManager;
}
@Override
public Event execute(RequestContext context) throws Exception {
val authentication = WebUtils.getAuthentication(context);
val devices = deviceManager.findRegisteredDevices(authentication.getPrincipal());
MultifactorAuthenticationWebflowUtils.putMultifactorAuthenticationRegisteredDevices(context, devices);
return new EventFactorySupport().event(this, CasWebflowConstants.TRANSITION_ID_SUCCESS);
}
@Override
public String getName() {
return "customDeviceProviderAction";
}
}
}Actions are typically integrated into webflows through configuration:
@Configuration
public class MyMfaWebflowActionsConfiguration {
@Bean
public Action myMfaCheckAvailableAction(ApplicationEventPublisher eventPublisher) {
return new MultifactorAuthenticationAvailableAction(eventPublisher);
}
@Bean
public Action myMfaCheckBypassAction(ApplicationEventPublisher eventPublisher) {
return new MultifactorAuthenticationBypassAction(eventPublisher);
}
@Bean
public Action myMfaFailureAction(ApplicationEventPublisher eventPublisher) {
return new MultifactorAuthenticationFailureAction(eventPublisher);
}
}These actions are then referenced in webflow definitions by their bean names and executed during state transitions.
Install with Tessl CLI
npx tessl i tessl/maven-org-apereo-cas--cas-server-core-webflow-mfa-api