Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 3 Next »

Flowchart of User Login and Forgot Password MFA escalations

The multi-factor authentication (MFA) flow works by triggering escalations in specific user processes. We have implemented MFA escalation in both the User Login and Forgot Password processes.

When these processes happen and MFA is required, the user is redirected to the MFA page and required to verify with an MFA factor. After verifying successfully via MFA, the user process will continue as usual. In the case of User Login, this will log the user in, and in the case of Forgot Password this will redirect the user to the change password screen.

If MFA verification fails, the user can retry up to the limit specified for the Account lockout threshold setting (Quick-access menu > Security). After this, the user account gets locked and the existing account lockout system can be used to recover the account.

A diagram displaying the User Login and Forgot Password processes for Totara multi-factor authentication.

Implementing an MFA plugin

Each MFA plugin provides a class named factor in the plugin namespace, extending \core_mfa\factor. This class provides information about the factor to the authentication system.

Generally you will want to override the following in the factor class:

  • $name – should be set to the plugin name, the part after mfa_

  • user_can_register() – return true if the user is able to register a new instance of this factor

  • get_register_component() – name of the Tui component to use for registration

  • get_register_data() – data to provide to the registration component

  • get_verify_component() – name of the Tui component to show on the login screen

  • get_verify_data() – extra data to pass to the verify component

  • verify() – called with data from the front end, when the verify component is submitted

All of these except for $name have default implementations, and can be skipped if they don’t apply.

On the front end, there are two components you’ll want to implement.

The first one is the Register component. This takes a data prop, containing the result of get_register_data(). This should call a plugin-specific GraphQL mutation in order to create an instance of the factor (see TOTP’s create_instance mutation for an example). It should then emit a saved event with the ID of the new instance.

The second is the Verify component. This takes three props: data (result of get_verify_data()), submitting, and submissionError. Verify should emit a submit event with a data object. The MFA code will then call verify() on the factor with the provided data object. submitting will be set to true while this happens, and submissionError will be set if an error is returned. See mfa_totp for an example of how these get used in practice.

There are also some language strings that will be needed:

  • pluginname – the name of the factor

  • factor_description – a description of the factor, used on factor selection screen (both verify and register)

  • verify_title – title to use on the verification screen

  • register_title – title to use on the register screen

Making an auth plugin MFA-compatible

With the implementation of process escalations for MFA, auth plugins need to make two key changes to support MFA.

Step 1: Create a complete user login callback

The complete_user_login function becomes non-returning when MFA is required. A new parameter ($complete_user_login_callback) has been added. Therefore auth plugins need to provide a complete_user_login_callback (which will contain any business logic used after the complete_user_login function).

An example of what this change looks like is shown below:

$user = authenticate_user_login($username, $key);
if($user) {
	complete_user_login($user);
	// update user profile picture
	custom_code_update_profle_picture($user, $a);

// update auth_plugin records
custom_code_update_other_records($user, $b);	
	redirect("https://totara/custom_url.php");
}

class custom_code_callback {
	public static function complete_login($a, $b) {
		global $USER;
		
		// Logged-in user
		$user = $USER;

	// update user profile picture
	custom_code_update_profle_picture($user, $a);

// update auth_plugin records
custom_code_update_other_records($user, $b);	
	redirect("https://totara/custom_url.php");
	}
}

$user = authenticate_user_login($username, $key);
if($user) {
	$callback = \core\login\complete_login_callback::create(
	[custom_code_callback::class, 'complete_login'],
	[$a, $b]
);

	complete_user_login($user,$callback);
}

The function/method provided to the complete_user_login_callback MUST be discoverable by the class autoloader.

Step 2: Override supports_mfa method

After creating the complete_user_login callback and testing your auth plugin works as expected, override the supports_mfa method in the auth plugin class to return true. This will help identify that the auth plugin now supports MFA in the system.

public static function supports_mfa(): bool {
        return false;
}

Revoking registered user factors

MFA can be revoked in two ways, and once the MFA is removed, logs events are triggered.

Via CLI

By using the revoke_admi_mfa CLI script, an admin user’s registered MFA factors can be revoked. The script prompts for the admin user’s username to find the user to revoke registered factors. You can also pass the username as a parameter to the script as shown below:

# Calling the script with a prompt

php server/admin/cli/revoke_admin_mfa.php

# Passing the username as a parameter

php server/admin/cli/revoke_admin_mfa.php --username=admin

Manage user login screen

Another way is to revoke a user’s registered MFA factors is through the Manage user login page. To perform this action, the logged-in user needs to have the moodle/user:managelogin capability. On this page, select the Rest MFA option under Action and click Update to revoke the admin user’s registered MFA factors.

  • No labels