External MFA Tutorial

With External MFA, developers can integrate their own services that handle MFA functionality independently from the online banking application. This modular approach enables easy integration with different systems and platforms, making it possible to support various authentication methods and workflows. In this tutorial we will walk through the process of creating an External MFA extension and setting up the Sandbox environment to test your extension.

For an overview on the External MFA flow, see our External MFA overview.

Note

External MFA is not the same thing as an External Auth Adapter. For clarification, see our guide on the differences between External Auth and External MFA.

First, let’s start by creating a new External MFA extension. External MFA extensions are Online extensions and can be either Server Side Rendered (SSR) or Client Side Rendered (CSR). For the purpose of this guide, we will create a Server Side Rendered extension.

$ q2 create_extension
New Extension Name: MyExternalMFA
What type of extension are you creating?

    1) Online (default) <---------
    2) SSO (Third Party Integration)
    3) Ardent (API)
    4) Q2Console (Backoffice)
    5) Central (Legacy Backoffice)
    6) Adapter
    7) Audit Action
    8) Custom Health Check
    9) Message Bus
    10) Caliper API Custom Endpoint
    11) Base Extension

Please make a selection and press Return [1]: 1

Select type of Online extension to generate

    1) Server Side Rendered (Default) <---------
    2) Client Side Rendered

Please make a selection and press Return: 1

Select type of Online integration to generate

    1) Authenticated (Default)
    2) Unauthenticated
    3) External MFA <---------

Please make a selection and press Return [1]: 3

Select type of MFA integration to generate

    1) Standard (Default) <---------
    2) Redirect-Based Authentication

Please make a selection and press Return [1]: 1

You should now see the generated boilerplate for your new External MFA extension. Let’s take a look at a couple things that differentiate this from your standard authenticated Online extension.

If you look at your MyExternalMFA/extension.py file you should see that we setup the IS_MFA flag as well as MFA_REQUIRES_REGISTRATION and MFA_TOKEN_LIFETIME_IN_MINUTES. The IS_MFA boolean is what allows your extension to be rendered in both a pre-authenticated state in login as well as an authenticated state during transaction authorization and event-driven validation.

# Allows the extension to be accessed during Online's MFA process
IS_MFA = True
# Determines if the MFA extension requires user registration to be accessed
MFA_REQUIRES_REGISTRATION = False
# Sets time until HQ expires the MFA token set by the extension
MFA_TOKEN_LIFETIME_IN_MINUTES = 5

The next part we’ll check out is the URLs generated in your configuration/MyExternalMFA.py file. These are specific to server-side rendered extensions. You will not see any difference in how URLs are handled for client-side rendered extensions.

FEATURE = {
    "core": False,
    "modules": {
        "Main": {
            "overpanel": False,
            "primary": True,
            "navigable": True,
            "formPostAuth": True,
            "url": "./sdk/mfa/{featureName}",
            "meta": {
                "type": {
                    "shape": "Content",
                    "context": "None",
                }
            }
        }
    }
}

Note

External MFA extensions always use the Main entrypoint of your extension regardless of whether they are server-side or client-side rendered. If you add additional entrypoints, they can only be accessed starting from the Main entrypoint.

Let’s also take a look at the generated MyExternalMFA/extension.py. You should see we’ve generated a placeholder function called token_handler.

async def token_handler(self):
    user_input = self.form_fields.get('userInput')

    if user_input != '47':
        return InternalServerError('Your entered value is incorrect. Please try again.')

    token = uuid.uuid4().hex

    return await self.set_external_mfa_token(token)

This function generates a token in the format of your choosing and sends it to HQ as a string. (In the placeholder we are generating a basic UUID.) Then we set the external token to be held in session by HQ with the self.set_external_mfa_token method and, if successful, return the created token to the front-end of your extension. In our example, we’ve added some additional server-side validation of user input coming from our form. Feel free to build upon or replace this function with your own as long as the end result is that you’ve set your token with HQ and returned it to the front-end.

Now, lets look at a very basic example of front-end handling in our index.html.jinja2 file that will load a form and call token_handler. Once submit is triggered, we’ll send the user input back for validation. If everything checks out we then kick off the MFA process. While the validation logic to kick off our MFA process is unimportant here, what is important is our handling to use tecton.sources.requestExtensionData to call out to our backend to generate and setup the token with HQ and passing the token back to the UUX platform with tecton.actions.verifyMFA.

<style>
    .wrapper {
        text-align: center;
    }
</style>

<div class="wrapper">
    <h1>Verification Check</h1>
    <h4>
        There is a patch of lily pads on a lake. Every day, the patch doubles in size.
        If it takes 48 days for the patch to cover the entire lake,
        how long would it take for the patch to cover half of the lake?
    </h4>
    <q2-input id="input" type="text"></q2-input>
    <q2-btn id='submit' intent="workflow-primary" block>Submit</q2-btn>
</div>

<script type="text/javascript">
    tecton.connected.then(function () {
        document.getElementById('submit').addEventListener('click', function () {
            const userInput = document.getElementById('input').value;
            tecton.actions.setFetching(true);
            tecton.sources.requestExtensionData({
                route: 'token_handler',
                body: { userInput }
            }).then((xhr) => {
                tecton.actions.verifyMFA({
                    token: xhr.data
                }).then(function (response) {
                    // Handle verifyMFA success response
                }).catch(function (error) {
                    // Handle verifyMFA error response
                });
            }).catch((xhr) => {
                tecton.actions.showModal({
                    title: 'Verification Failed',
                    message: xhr.message || "We've encountered an error. Please try again.",
                    modalType: "warning",
                });
            });
        });
    });
</script>

Now lets install your new External MFA extension.

$ q2 install

Which Extension would you like to install?

1) MyExternalMFA

Please make a selection and press Return: 1

Querying groups from database

Select groups to grant form access:

    1) [x] Retail Users
    2) [ ] Commercial Users
    3) [ ] Merchant Verification
    4) [x] Q2 Test Group - DO NOT DELETE
    5) [ ] Q2 Retail Default
    6) [ ] Q2 Retail ExternalTransfer
    7) [ ] Q2 Retail PositivePay
    8) [ ] Q2 Retail MobileCapture
    9) [ ] RetailUserRequiresMfa_NoLogonNoAccounts

Select a number to toggle. 'a' for All. 'b' to set all to Blank. Leave empty to continue:

Extension Display Name [MyExternalMFA]:
Successfully installed MyExternalMFA extension as form_id 1!

Now, not only have you installed your MFA extension, but a new record has been added to the Q2_MFAProvider table in the database.

Setting up Sandbox

To test your extension in the Sandbox environment, let’s enable External MFA for our test user. You can replace retail0 with the user of your choice.

$ q2 sandbox external_mfa enable -n retail0

We’ll also need to setup a Q2_MFAGroupProfile to tie our MFA Provider to the different MFA workflows. In the CLI we enable our new MFA provider to be used in all MFA flows, but you may create profiles that mix, match, and omit MFA providers for the Authentication, Transaction Authentication, and EDV (Patrol) workflows as needed.

$ q2 db add_mfa_group_profile
Please provide a description for this MFA group: My MFA Group Profile

Select MFA Provider for this MFA Group Profile

    1) MyExternalMFA

Please make a selection and press Return: 1
MyExternalMFA

Finally, we need to tie our user’s group to the newly created MFA Group Profile. Using q2 db update_group_to_mfa_group_profile, you will be prompted to select Groups to add to a selected MFA Group Profile.

$ q2 db update_group_to_mfa_group_profile
Select MFA Group Profile

    1) My MFA Group Profile

Please make a selection and press Return: 1
My MFA Group Profile

Select Groups for MFA Group Profile

    1) [x] Retail Users
    2) [ ] Commercial Users
    3) [ ] Merchant Verification
    4) [ ] Q2 Test Group - DO NOT DELETE
    5) [ ] Q2 Retail Default
    6) [ ] Q2 Retail ExternalTransfer
    7) [ ] Q2 Retail PositivePay
    8) [ ] Q2 Retail MobileCapture
    9) [ ] RetailUserRequiresMfa_NoLogonNoAccounts

Select a number to toggle. 'a' for All. 'b' to set all to Blank. Leave empty to continue: 1

Once you’ve completed the setup, you should be able to test your extension in the Sandbox environment.

../../_images/external_mfa_example.gif