Trying out Auth0 Actions to build Conditional MFA
Tags: mfa, auth0, authentication, auth
Published: 2021-02-27 07:17:52

# Conditional MFA

Multi-Factor Authentication (MFA) is the process of adding extra steps into your authentication pipeline to ensure that your users are who they say they are and prevent malicious actors from impersonating legitimate users. The downside of MFA is that it adds extra friction for the user. One way to alleviate this friction is to only require MFA in certain circumstances. This is often called Adaptive MFA, here I'm going to use the term "Conditional MFA" because Auth0 has a paid feature called Adaptive MFA. What I'll be running through here is how to force your users to do an MFA login only under certain circumstances while staying within the Auth0 free tier and trying out Auth0's Actions product which is currently in Beta.

# Set up Auth0

If you want to know how to set up Auth0 for MFA using Auth0 Guardian go through [the previous blog in this series](https://dev.to/kleeut/setting-up-mfa-with-auth0-guardian-2fli). The only thing to change from the setup in this previous post is to turn MFA to _Never_ for everyone. Rather than pushing it on all users we will be making a decision at login time about if they need to complete an MFA check.

![Set MFA to off](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zuq082w9zhfe8yjk106s.png)

# A side note on Auth0 Actions

Auth0 Actions are a new feature of Auth0 that allow you to further customise the authentication pipeline by adding your own code to be run after each log in event. They're currently in Beta so I thought I would give them a try for creating a conditional authentication experience. The alternative is Rules. Actions come along with a few advantages including a more up to date editor and the ability to include 3rd party packages from npm.

## Type Checking

One of the advantages of the new editor for Actions is that it includes types checking. This really helps to make things more discoverable.

Unfortunately there are some rough edges on the type checking. An example of this is that the `event.actor.query.scope` property is a union type that the editor reports as not supporting string operations.

![Showing a type error on scope](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oq5pitgdy5o50paa1osg.png)
![Scope error detail tool tip](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ih9w7crgmja2i5mteujp.png)

Even if the editor is showing errors it wont stop the code from running. There is not compilation step that confirms the code passes type checking before being run in the pipeline.

I expect that these are just rough edges that will get knocked off before they are out of Beta.

## Adding actions to login

To add an action into the login flow you need to press the deploy button at the top right of the actions editor. Then navigate to the Flows page via the menu.

![Flow Menu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofm8c93ewaajm8vx6jj2.png)

In the flows page select Login to go to the flow of actions related to user login.

![Flow Menu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5i38c6hdabuwrqbxxzp.png)

Then drag your action into the flow.

![Drag action into the flows](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n1hng3urc6h5gq6igrz7.png)

Finally hit apply and the action will be deployed into the flow.

For future changes to the same action hitting deploy in the actions editor will update the version in the flow.

## Debugging

Debugging of Rules or Actions can be difficult, but there are a couple of built in ways that can be used to support your development workflow.

### Test events

Actions have test events that can be run through the pipeline to ensure correctness.
The output will return the object returned, some stats about the execution, and any `console.log` output.

```json
{
  "payload": {
    "output": {
      "command": {
        "provider": "guardian",
        "type": "multifactor"
      }
    },
    "logs": "Sample log #1\nip address 13.33.86.47 is concerning better do an MFA check.\n",
    "stats": {
      "action_duration_ms": 46,
      "boot_duration_ms": 60,
      "network_duration_ms": 47
    }
  }
}
```

### Logging production data

In production as members log some data about the actions that were run are included in a new Actions tab in the logs view. Before relying on this for your debugging workflows you need to be aware that the logs are truncated. It's not clear if this is a feature or a bug in the beta. At the moment I'm not able to work out what the trigger is for log truncation.

### Real-time Webtask Logs

The Real-time webtask Logs extension can also be used to debug actions. This extension gives you a temporary view into what is happening in your Actions (or Rules). It's a good tool when there is a low amount of login activity. As the amount of activity goes up the level of noise quickly makes this method of little use.

# Building the Action

Enough about what actions are, how can we use them to ensure only some of the users need to do MFA?

For this example lets make an assumption that the CTO of our organisation has a real paranoia of the number 13. They're convinced that anyone logging in from an IP Address that has a the number `13` in it anywhere may have had their account compromised and we need to force them to do an MFA check. Let's have a look at how do to that with Auth0 Actions.

## Parse the input

The action provides us with two properties, the `event` and the `context`.
The `event` object has the users IP Address available under the `actor` property.

```js
const ipAddress = event.actor.ip;
```

We can then use that to check if it includes a `13` any where.

```js
if (ipAddress.inclues("13")) {
  // Force MFA
  console.log(`IP Address ${ipAddress} is concerning better do an MFA check.`);
}
// otherwise continue
console.log("This person is obviously trustworthy let them in.");
```

## Require MFA

In order to require MFA we need to return an object with a `command` property which instructs Auth0 that before this user can log in they need to do a MFA check.

To do that add this into the positive case of the if block.

```js
return {
  command: {
    type: "multifactor",
    provider: "guardian",
  },
};
```

If they don't need to do a MFA check we can just return an empty object and the Actions engine will move on to the next action in the flow.

## Final action code

My final action code looks like this:

```js
module.exports = async (event, context) => {
  console.log("Sample Log #1");
  const ipAddress = event.actor.ip;
  if (ipAddress.includes("13")) {
    console.log(
      `ip address ${ipAddress} is concerning better do an MFA check.`
    );
    return {
      command: {
        type: "multifactor",
        provider: "guardian",
      },
    };
  }
  console.log("This person is obviously trustworthy let them in.");
  return {};
};
```

With that, when a user attempts to log in from an IP Address with `"13"` in it they will be required to prove who they are using Auth0 Guardian.

## Enrolling MFA

A point to note here is that the first time they are required to do an MFA login they will be asked to enrol with Guardian. If you want to force the user to enrol on first login you will need to add some extra code into the action.

Create a new custom action to handle forcing a new user into the MFA flow. In an action we can find out the number of logins done by a user by checking the `event.stats.loginsCount`. The 2nd action needs to check this property and return a `guardian` mfa command if it is the first login `event.stats.loginsCount === 1`.

```js
module.exports = async (event, context) => {
  if (event.stats.loginsCount === 1) {
    return {
      command: {
        type: "multifactor",
        provider: "guardian",
      },
    };
  }
  return {};
};
```

With this every new sign up will be redirected to set up MFA. This means that if in the future they log in from an ip address that includes `13` then the user can prove who they are using Guardian MFA.

# Wrap up

Actions provide a simple way to extend the Authentication pipeline. In particular here we can see that they can quickly be used to create a conditional MFA requirement for some users. The example of requiring people with `13` in their ip address is obviously a bit of an over simplification. The `actor` property on the `event` parameter also has information on geo-location of the ip address which could be used with impossible travel algorithms to check that your user isn't being impersonated by a malicious actor on the other side of the world. That's probably a better condition for MFA.

Cover Photo by [Jen Theodore](https://unsplash.com/@jentheodore?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) [Unsplash](https://unsplash.com/s/photos/multiple?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)