Core API for multifactor authentication webflow configuration in Apereo CAS providing interfaces and base classes for MFA provider integration
—
Static utility methods for managing MFA-related data in webflow scopes, device registration, provider selection, and token management. The MultifactorAuthenticationWebflowUtils class provides a comprehensive set of utilities for storing and retrieving MFA-related information across webflow transitions.
Utility class providing static methods for managing MFA-related data in webflow scopes.
/**
* Utility class for managing MFA-related data in webflow scopes
*/
@UtilityClass
public class MultifactorAuthenticationWebflowUtils {
// Webflow Customizer Management
/**
* Get multifactor authentication webflow customizers from application context
* @param applicationContext Spring application context
* @return List of sorted webflow customizers
*/
public static List<CasMultifactorWebflowCustomizer> getMultifactorAuthenticationWebflowCustomizers(
ConfigurableApplicationContext applicationContext);
// Device Registration Management
/**
* Check if multifactor device registration is enabled
* @param requestContext The webflow request context
* @return true if device registration is enabled
*/
public static boolean isMultifactorDeviceRegistrationEnabled(RequestContext requestContext);
/**
* Set multifactor device registration enabled flag
* @param requestContext The webflow request context
* @param enabled Whether device registration is enabled
*/
public static void putMultifactorDeviceRegistrationEnabled(RequestContext requestContext, boolean enabled);
// Provider Management
/**
* Store resolved multifactor authentication providers in conversation scope
* @param context The webflow request context
* @param value Collection of resolved MFA providers
*/
public static void putResolvedMultifactorAuthenticationProviders(
RequestContext context,
Collection<MultifactorAuthenticationProvider> value);
/**
* Get resolved multifactor authentication providers from conversation scope
* @param context The webflow request context
* @return Collection of resolved provider IDs
*/
public static Collection<String> getResolvedMultifactorAuthenticationProviders(RequestContext context);
/**
* Store MFA provider ID in flow scope
* @param context The webflow request context
* @param provider The MFA provider
*/
public static void putMultifactorAuthenticationProvider(RequestContext context, MultifactorAuthenticationProvider provider);
/**
* Get MFA provider ID from flow scope
* @param context The webflow request context
* @return Provider ID string
*/
public static String getMultifactorAuthenticationProvider(RequestContext context);
// Provider Selection Management
/**
* Store selectable multifactor authentication providers in view scope
* @param requestContext The webflow request context
* @param mfaProviders List of selectable provider IDs
*/
public static void putSelectableMultifactorAuthenticationProviders(RequestContext requestContext, List<String> mfaProviders);
/**
* Get selectable multifactor authentication providers from view scope
* @param requestContext The webflow request context
* @return List of selectable provider IDs
*/
public static List<String> getSelectableMultifactorAuthenticationProviders(RequestContext requestContext);
// Google Authenticator Specific
/**
* Set Google Authenticator multiple device registration enabled flag
* @param requestContext The webflow request context
* @param enabled Whether multiple device registration is enabled
*/
public static void putGoogleAuthenticatorMultipleDeviceRegistrationEnabled(RequestContext requestContext, boolean enabled);
/**
* Check if Google Authenticator multiple device registration is enabled
* @param requestContext The webflow request context
* @return Boolean indicating if multiple device registration is enabled
*/
public static Boolean isGoogleAuthenticatorMultipleDeviceRegistrationEnabled(RequestContext requestContext);
// YubiKey Specific
/**
* Set YubiKey multiple device registration enabled flag
* @param requestContext The webflow request context
* @param enabled Whether multiple device registration is enabled
*/
public static void putYubiKeyMultipleDeviceRegistrationEnabled(RequestContext requestContext, boolean enabled);
// Simple MFA Token Management
/**
* Store simple multifactor authentication token in flow scope
* @param requestContext The webflow request context
* @param token The ticket token
*/
public static void putSimpleMultifactorAuthenticationToken(RequestContext requestContext, Ticket token);
/**
* Remove simple multifactor authentication token from flow scope
* @param requestContext The webflow request context
*/
public static void removeSimpleMultifactorAuthenticationToken(RequestContext requestContext);
/**
* Get simple multifactor authentication token from flow scope
* @param <T> Type of ticket extending Ticket
* @param requestContext The webflow request context
* @param clazz Class type of the ticket
* @return Typed ticket instance or null
*/
public static <T extends Ticket> T getSimpleMultifactorAuthenticationToken(RequestContext requestContext, Class<T> clazz);
// Parent Credential Management
/**
* Get multifactor authentication parent credential from flow scope
* @param requestContext The webflow request context
* @return Parent credential or null
*/
public static Credential getMultifactorAuthenticationParentCredential(RequestContext requestContext);
// Registered Device Management
/**
* Store multifactor authentication registered devices in flow scope
* @param requestContext The webflow request context
* @param accounts Set of registered devices/accounts
*/
public static void putMultifactorAuthenticationRegisteredDevices(RequestContext requestContext, Set accounts);
/**
* Get multifactor authentication registered devices from flow scope
* Note: This method is NOT static in the source, which appears to be an inconsistency
* @param requestContext The webflow request context
* @return Set of registered devices
*/
public Set<MultifactorAuthenticationRegisteredDevice> getMultifactorAuthenticationRegisteredDevices(RequestContext requestContext);
// One-Time Token Account Management
/**
* Store one-time token account in flow scope
* @param requestContext The webflow request context
* @param account The one-time token account
*/
public static void putOneTimeTokenAccount(RequestContext requestContext, OneTimeTokenAccount account);
/**
* Store collection of one-time token accounts in flow scope
* @param requestContext The webflow request context
* @param accounts Collection of one-time token accounts
*/
public static void putOneTimeTokenAccounts(RequestContext requestContext, Collection accounts);
/**
* Get one-time token accounts from flow scope
* @param requestContext The webflow request context
* @return Collection of one-time token accounts
*/
public static Collection getOneTimeTokenAccounts(RequestContext requestContext);
/**
* Get typed one-time token account from flow scope
* @param <T> Type extending OneTimeTokenAccount
* @param requestContext The webflow request context
* @param clazz Class type of the account
* @return Typed account instance or null
*/
public static <T extends OneTimeTokenAccount> T getOneTimeTokenAccount(RequestContext requestContext, Class<T> clazz);
}// In a webflow action
public class MyMfaAction extends BaseCasWebflowAction {
@Override
protected Event doExecuteInternal(RequestContext requestContext) throws Exception {
// Get current MFA provider
val providerId = MultifactorAuthenticationWebflowUtils
.getMultifactorAuthenticationProvider(requestContext);
if (providerId == null) {
// No provider set, determine and store one
val selectedProvider = determineAppropriateProvider(requestContext);
MultifactorAuthenticationWebflowUtils
.putMultifactorAuthenticationProvider(requestContext, selectedProvider);
}
return success();
}
}// Enable/disable device registration based on policy
public class DeviceRegistrationPolicyAction extends BaseCasWebflowAction {
@Override
protected Event doExecuteInternal(RequestContext requestContext) throws Exception {
val authentication = WebUtils.getAuthentication(requestContext);
val service = WebUtils.getService(requestContext);
// Check policy for device registration
val deviceRegistrationEnabled = shouldAllowDeviceRegistration(authentication, service);
// Store the decision in webflow scope
MultifactorAuthenticationWebflowUtils
.putMultifactorDeviceRegistrationEnabled(requestContext, deviceRegistrationEnabled);
// Provider-specific settings
if (isGoogleAuthenticatorProvider()) {
MultifactorAuthenticationWebflowUtils
.putGoogleAuthenticatorMultipleDeviceRegistrationEnabled(requestContext,
shouldAllowMultipleDevices(authentication));
}
return success();
}
}// Prepare providers for user selection
public class PrepareProviderSelectionAction extends BaseCasWebflowAction {
@Override
protected Event doExecuteInternal(RequestContext requestContext) throws Exception {
val authentication = WebUtils.getAuthentication(requestContext);
val availableProviders = getAvailableProvidersForUser(authentication);
// Filter providers based on availability and bypass rules
val selectableProviders = availableProviders.stream()
.filter(provider -> isProviderAvailable(provider, authentication))
.filter(provider -> !shouldBypassProvider(provider, authentication))
.map(MultifactorAuthenticationProvider::getId)
.collect(Collectors.toList());
// Store selectable providers for the view
MultifactorAuthenticationWebflowUtils
.putSelectableMultifactorAuthenticationProviders(requestContext, selectableProviders);
if (selectableProviders.isEmpty()) {
return error();
} else if (selectableProviders.size() == 1) {
// Only one provider available, set it directly
val singleProvider = getProviderById(selectableProviders.get(0));
MultifactorAuthenticationWebflowUtils
.putMultifactorAuthenticationProvider(requestContext, singleProvider);
return new EventFactorySupport().event(this, "single");
} else {
// Multiple providers available, proceed to selection
return success();
}
}
}// Handle simple MFA token lifecycle
public class SimpleMfaTokenAction extends BaseCasWebflowAction {
private final TicketRegistry ticketRegistry;
@Override
protected Event doExecuteInternal(RequestContext requestContext) throws Exception {
val authentication = WebUtils.getAuthentication(requestContext);
// Generate and store token
val token = createSimpleMfaToken(authentication);
ticketRegistry.addTicket(token);
MultifactorAuthenticationWebflowUtils
.putSimpleMultifactorAuthenticationToken(requestContext, token);
// Send token to user (SMS, email, etc.)
sendTokenToUser(token, authentication.getPrincipal());
return success();
}
// In verification action
protected Event verifyToken(RequestContext requestContext) throws Exception {
val submittedToken = requestContext.getRequestParameters().get("token");
val storedToken = MultifactorAuthenticationWebflowUtils
.getSimpleMultifactorAuthenticationToken(requestContext, SimpleMfaToken.class);
if (storedToken != null && storedToken.getId().equals(submittedToken)) {
// Token verified, clean up
MultifactorAuthenticationWebflowUtils
.removeSimpleMultifactorAuthenticationToken(requestContext);
ticketRegistry.deleteTicket(storedToken.getId());
return success();
}
return error();
}
}// Load and manage registered devices
public class LoadRegisteredDevicesAction extends BaseCasWebflowAction {
private final MultifactorAuthenticationDeviceManager deviceManager;
@Override
protected Event doExecuteInternal(RequestContext requestContext) throws Exception {
val authentication = WebUtils.getAuthentication(requestContext);
val principal = authentication.getPrincipal();
// Load registered devices for the user
val registeredDevices = deviceManager.findRegisteredDevices(principal);
// Store in webflow scope for view access
MultifactorAuthenticationWebflowUtils
.putMultifactorAuthenticationRegisteredDevices(requestContext, registeredDevices);
// Also store as one-time token accounts if applicable
val tokenAccounts = registeredDevices.stream()
.filter(device -> device instanceof OneTimeTokenAccount)
.map(device -> (OneTimeTokenAccount) device)
.collect(Collectors.toSet());
if (!tokenAccounts.isEmpty()) {
MultifactorAuthenticationWebflowUtils
.putOneTimeTokenAccounts(requestContext, tokenAccounts);
}
return success();
}
}// Custom utility methods extending the base utilities
@UtilityClass
public class CustomMfaWebflowUtils {
/**
* Store custom MFA context data
*/
public static void putCustomMfaContext(RequestContext context, String key, Object value) {
context.getFlowScope().put("customMfa_" + key, value);
}
/**
* Get custom MFA context data
*/
public static <T> T getCustomMfaContext(RequestContext context, String key, Class<T> type) {
return context.getFlowScope().get("customMfa_" + key, type);
}
/**
* Check if user has completed MFA recently (using conversation scope for session-wide storage)
*/
public static boolean hasRecentMfaCompletion(RequestContext context, String providerId) {
val completions = context.getConversationScope()
.get("recentMfaCompletions", Map.class);
if (completions != null) {
val lastCompletion = (Long) completions.get(providerId);
return lastCompletion != null &&
(System.currentTimeMillis() - lastCompletion) < Duration.ofMinutes(5).toMillis();
}
return false;
}
/**
* Record MFA completion timestamp
*/
public static void recordMfaCompletion(RequestContext context, String providerId) {
val completions = context.getConversationScope()
.get("recentMfaCompletions", Map.class);
val completionMap = completions != null ? completions : new HashMap<String, Long>();
completionMap.put(providerId, System.currentTimeMillis());
context.getConversationScope().put("recentMfaCompletions", completionMap);
}
}The utility class automatically manages the appropriate scopes for different types of MFA data, ensuring optimal performance and proper cleanup.
Install with Tessl CLI
npx tessl i tessl/maven-org-apereo-cas--cas-server-core-webflow-mfa-api