January 25, 2025

Using Action Sets in Keycloak Authentication: Configuring Multi-Step Flows

Using Action Sets in Keycloak Authentication: Configuring Multi-Step Flows

Keycloak offers powerful mechanisms to implement custom authentication flows using action sets and authentication sessions. With this flexibility, you can create both simple and advanced authentication processes, such as passwordless login, multi-factor authentication (MFA), and OTP-based verification. This guide provides a detailed walkthrough on configuring these action sets, alongside best practices and advanced strategies to maximize security and usability.


What Are Action Sets in Keycloak?

Action sets in Keycloak define the individual steps that comprise an authentication flow. By combining and sequencing these steps, you can create tailored workflows that meet specific requirements.

Key Concepts

  1. Action: A single step in an authentication process, such as verifying credentials or sending an OTP.
  2. Action Set: A group of actions that belong to an authentication step.
  3. Execution Flow: Defines the sequence in which actions are executed.

Configuring Multi-Step Authentication Flows

Example 1: Two-Step Authentication Flow (Password + OTP)

Step 1: Validate Password

  • Action: PasswordForm
    • Accepts the username and password from the user for validation.
    • Execution Requirement: Required

Step 2: Validate OTP

  • Action: OTPForm
    • Sends an OTP to the user’s registered email or phone and validates it.
    • Execution Requirement: Required

Configuration Example

{
  "alias": "Password + OTP",
  "authenticationExecutions": [
    {
      "authenticator": "password-form",
      "requirement": "REQUIRED",
      "priority": 10
    },
    {
      "authenticator": "otp-form",
      "requirement": "REQUIRED",
      "priority": 20
    }
  ]
}

Example 2: Passwordless Login with Device Trust

Step 1: Username/Email Input

  • Action: UserNameForm
    • Allows the user to input their username or email.
    • Execution Requirement: Required

Step 2: OTP Validation

  • Action: OTPForm
    • Sends and validates an OTP for passwordless login.
    • Execution Requirement: Required

Step 3: Device Trust Validation

  • Action: DeviceTrustCheck
    • Verifies if the login is from a trusted device or browser.
    • Execution Requirement: Conditional (skipped for trusted devices)

Configuration Example

{
  "alias": "Passwordless with Device Trust",
  "authenticationExecutions": [
    {
      "authenticator": "username-form",
      "requirement": "REQUIRED",
      "priority": 10
    },
    {
      "authenticator": "otp-form",
      "requirement": "REQUIRED",
      "priority": 20
    },
    {
      "authenticator": "device-trust-check",
      "requirement": "CONDITIONAL",
      "priority": 30
    }
  ]
}

Configuring Action Sets in the Keycloak Admin Console

Step 1: Create a New Authentication Flow

  1. Navigate to Authentication in the Keycloak Admin Console.
  2. Click on the Flows tab.
  3. Click New to create a custom authentication flow.
    • Alias: Enter a name for your flow (e.g., Password + OTP).
    • Built-In: Choose whether this flow is built-in or not (leave unchecked for custom flows).

Step 2: Add Execution Steps

  1. Select your newly created flow and click Add Execution.
  2. In the Add Execution dialog, choose an authenticator (e.g., password-form, otp-form).
  3. Set the Requirement:
    • Required: Mandatory step.
    • Conditional: Executed only if conditions are met.
    • Alternative: One of several possible steps.
  4. Save your changes.

Step 3: Configure Execution Order

  1. Use the Priority field to define the order of execution.
  2. Lower numbers are executed first.

Step 4: Save and Test

  1. Assign the custom flow to the desired client or realm under Bindings.
  2. Test the flow by logging in with a user account.

Dynamically Updating Action Sets in Custom Sign-In Flows

Keycloak allows for dynamic updates to action sets, enabling custom sign-in flows that adapt based on real-time conditions. For instance, you can programmatically determine which actions or steps to include in a flow based on user roles, device type, or risk assessment.

Example: Dynamic Action Injection

Use Case: Adding an OTP Step for High-Risk Logins

Suppose you want to dynamically add an OTP validation step for users logging in from unknown devices or high-risk locations.

Implementation Steps:

  1. Create a Custom Authenticator: Implement a custom authenticator to evaluate conditions and modify the flow dynamically.
public class DynamicActionAuthenticator implements Authenticator {

    @Override
    public void authenticate(AuthenticationFlowContext context) {
        boolean isHighRisk = evaluateRisk(context);

        if (isHighRisk) {
            context.getAuthenticationSession().setAuthNote("add-otp-step", "true");
        }
        context.success();
    }

    private boolean evaluateRisk(AuthenticationFlowContext context) {
        // Implement logic to evaluate risk (e.g., check IP, device trust, or user behavior).
        return true; // Example: Treat all logins as high-risk for demonstration.
    }
}
  1. Modify the Flow Based on Conditions: In subsequent steps, check the AuthNote to determine whether to include additional actions.
public class ConditionalOTPAuthenticator implements Authenticator {

    @Override
    public void authenticate(AuthenticationFlowContext context) {
        String addOtpStep = context.getAuthenticationSession().getAuthNote("add-otp-step");

        if ("true".equals(addOtpStep)) {
            String otp = generateOtp();
            context.getAuthenticationSession().setAuthNote("otp", otp);
            sendOtpToUser(context.getUser(), otp);
            context.challenge(context.form().createForm("otp-form.ftl"));
        } else {
            context.success();
        }
    }

    @Override
    public void action(AuthenticationFlowContext context) {
        String inputOtp = context.getHttpRequest().getDecodedFormParameters().getFirst("otp");
        String sessionOtp = context.getAuthenticationSession().getAuthNote("otp");

        if (inputOtp != null && inputOtp.equals(sessionOtp)) {
            context.success();
        } else {
            context.failure(AuthenticationFlowError.INVALID_CREDENTIALS);
        }
    }
}
  1. Configure the Flow: Add the DynamicActionAuthenticator as the first step in your custom flow and ConditionalOTPAuthenticator as a later step. Ensure the OTP step is configured as Conditional.

Configuring Action Sets in a Custom Provider JAR

To extend Keycloak with custom authentication logic, you can package your custom authenticators and action sets in a provider JAR.

Steps to Create a Provider JAR

1. Implement Custom Authenticators

Create Java classes that implement the Authenticator or AuthenticatorFactory interfaces.

public class CustomAuthenticator implements Authenticator {
    @Override
    public void authenticate(AuthenticationFlowContext context) {
        // Custom authentication logic
        context.success();
    }

    @Override
    public void action(AuthenticationFlowContext context) {
        // Action to perform during this step
    }

    @Override
    public void close() {
        // Clean-up resources if necessary
    }
}

2. Register Authenticators in a Factory

Provide metadata and configuration for your authenticators.

public class CustomAuthenticatorFactory implements AuthenticatorFactory {
    @Override
    public Authenticator create(KeycloakSession session) {
        return new CustomAuthenticator();
    }

    @Override
    public String getId() {
        return "custom-authenticator";
    }

    @Override
    public void init(Config.Scope config) {}

    @Override
    public void postInit(KeycloakSessionFactory factory) {}

    @Override
    public void close() {}
}

3. Package the Provider as a JAR

  • Compile your classes and package them into a JAR file.
  • Include a META-INF/services/org.keycloak.authentication.AuthenticatorFactory file listing your factory class.

4. Deploy the JAR

  • Copy the JAR file to the providers directory of your Keycloak server.
  • Restart Keycloak to load the custom provider.

5. Configure in Admin Console

  • Your custom authenticators will now appear as options in the Add Execution dialog when configuring authentication flows.

Best Practices for Multi-Step Authentication

  1. Simplify User Experience

    • Reduce the number of steps to the minimum required for security.
    • Use conditional actions to skip unnecessary steps (e.g., skipping OTP for trusted devices).
  2. Enhance Security

    • Validate inputs at every step.
    • Use short-lived tokens for OTPs to mitigate replay attacks.
    • Implement rate limiting to prevent brute-force attacks.
  3. Leverage Authentication Sessions

    • Use AuthenticationSessionModel to persist data across steps (e.g., OTP codes, device trust flags).
    • Store temporary data in session attributes rather than fetching it repeatedly from the database.
  4. Handle Errors Gracefully

    • Display user-friendly error messages.
    • Log errors using Keycloak’s event logging system to aid troubleshooting.

Advanced Strategies for Custom Flows

Persisting State Across Steps

Use AuthenticationSessionModel to manage temporary state during multi-step authentication.

Example: OTP Storage and Validation

public class OTPAuthenticator implements Authenticator {

    @Override
    public void authenticate(AuthenticationFlowContext context) {
        String otp = generateOtp();
        AuthenticationSessionModel session = context.getAuthenticationSession();
        session.setAuthNote("otp", otp);
        sendOtpToUser(context.getUser(), otp);
        context.challenge(context.form().createForm("otp-form.ftl"));
    }

    @Override
    public void action(AuthenticationFlowContext context) {
        String inputOtp = context.getHttpRequest().getDecodedFormParameters().getFirst("otp");
        String sessionOtp = context.getAuthenticationSession().getAuthNote("otp");

        if (inputOtp != null && inputOtp.equals(sessionOtp)) {
            context.success();
        } else {
            context.failure(AuthenticationFlowError.INVALID_CREDENTIALS);
        }
    }
}

Conclusion

By leveraging action sets and authentication sessions, Keycloak enables the creation of secure, user-friendly, and flexible authentication flows. Whether implementing simple two-step verification or advanced passwordless login with device trust, following the best practices outlined in this guide will help ensure robust and reliable authentication processes. Start experimenting with custom flows to unlock the full potential of Keycloak!