> ## Documentation Index
> Fetch the complete documentation index at: https://docs.authsignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Adding adaptive MFA to your Cognito login flow

> Learn how to add adaptive MFA to your Cognito login flow using Authsignal.

Adaptive MFA can be achieved by utilizing Authsignal's rules engine.
For example, you can create [risk-based authentication flows](/actions-rules/rules/getting-started#creating-a-rule-to-challenge-high-risk-users) so that MFA is only required when certain conditions are met e.g. if a user is authenticating on a new device.
For more bespoke scenarios, you can also integrate with your [business-specific data points](/actions-rules/rules/custom-data-points).

This guide will cover how to modify your Authsignal integration with Cognito to utilize rules.

## Adaptive MFA on login

A common use case for adaptive MFA is on login.
For example, we might want to reduce user friction by not requiring MFA for users who are authenticating from a known device.

### Authsignal rule set up

1. Go to the [Actions page](https://portal.authsignal.com/actions) and find the **Cognito Auth** action.
2. Click on the **Cognito Auth** action go to the **Rules** tab.
3. Click on the **Create Rule** button and create a new rule. For example, a [new device rule](/actions-rules/rules/adaptive-mfa#new-device-detection)

<Note>
  The **Cognito Auth** action will only appear if you have tested your existing Authsignal + Cognito
  integration. If you don't see it, you can use the **Configure a new action** button to create it.
  Make sure to name it `cognitoAuth` so it matches the value in your lambda code.
</Note>

### Define Auth Challenge lambda

In this case we assume the [Define Auth Challenge lambda](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-define-auth-challenge.html) is implemented to require multiple authentication steps.

```ts theme={null}
import { DefineAuthChallengeTriggerHandler } from "aws-lambda";

export const handler: DefineAuthChallengeTriggerHandler = async (event) => {
  const { session } = event.request;

  if (session.length === 1 && session[0].challengeName === "SRP_A") {
    event.response.issueTokens = false;
    event.response.failAuthentication = false;
    event.response.challengeName = "PASSWORD_VERIFIER";
  } else if (session.length === 2 && session[1].challengeName === "PASSWORD_VERIFIER") {
    event.response.issueTokens = false;
    event.response.failAuthentication = false;
    event.response.challengeName = "CUSTOM_CHALLENGE";
  } else if (
    session.length === 3 &&
    session[2].challengeName === "CUSTOM_CHALLENGE" &&
    session[2].challengeResult === true
  ) {
    event.response.issueTokens = true;
    event.response.failAuthentication = false;
  } else {
    event.response.issueTokens = false;
    event.response.failAuthentication = true;
  }

  return event;
};
```

The `"CUSTOM_CHALLENGE"` step is delegated to Authsignal.

### Create Auth Challenge lambda

When tracking our action in the [Create Auth Challenge lambda](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-create-auth-challenge.html) we can check the `state` field in the response to see whether our rule has determined that a challenge is required for the action.

In addition to the `url` for the pre-built UI, we will also pass this `state` value along with a `token` back to our app as public challenge parameters.

```ts theme={null}
import { Authsignal } from "@authsignal/node";
import { CreateAuthChallengeTriggerHandler } from "aws-lambda";

const authsignal = new Authsignal({
  apiSecretKey: process.env.AUTHSIGNAL_SECRET,
  apiUrl: process.env.AUTHSIGNAL_URL,
});

export const handler: CreateAuthChallengeTriggerHandler = async (event) => {
  const userId = event.request.userAttributes.sub;
  const email = event.request.userAttributes.email;

  const { state, token, url } = await authsignal.track({
    action: "cognitoAuth",
    userId,
    attributes: {
      email,
    },
  });

  event.response.publicChallengeParameters = { state, token, url };

  return event;
};
```

<Note>
  Make sure to include any additional data points required by the rule you set up on your **Cognito
  Auth** action.
</Note>

### The app code

Now we can adapt our app code to use the `state` param to determine whether MFA is required.

```ts theme={null}
// Sign in with username and password using Amplify
const { nextStep } = await signIn({
  username: email,
  password,
  options: {
    authFlowType: "CUSTOM_WITH_SRP",
  },
});

const { state, token, url } = nextStep.additionalInfo;

if (state === "ALLOW") {
  // No MFA is required
  // Pass the initial token to confirm sign-in
  await confirmSignIn({ challengeResponse: token });
} else {
  // MFA is required
  // Launch the pre-built UI to obtain a validation token for an Authsignal challenge
  const response = await authsignal.launch(url, { mode: "popup" });

  await confirmSignIn({ challengeResponse: response.token });
}
```

### Verify Auth Challenge Response lambda

Finally, we need to update our [Verify Auth Challenge Response lambda](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-verify-auth-challenge-response.html) to handle if the user is allowed to bypass MFA.

The only change here is to set `event.response.answerCorrect` to true if the `state` of the action is either `"CHALLENGE_SUCCEEDED"` (because the user successfully completed an MFA step) or `"ALLOW"` (because the user wasn't required to complete MFA).

```ts theme={null}
import { Authsignal, UserActionState } from "@authsignal/node";
import { VerifyAuthChallengeResponseTriggerHandler } from "aws-lambda";

const authsignal = new Authsignal({
  apiSecretKey: process.env.AUTHSIGNAL_SECRET,
  apiUrl: process.env.AUTHSIGNAL_URL,
});

export const handler: VerifyAuthChallengeResponseTriggerHandler = async (event) => {
  const userId = event.request.userAttributes.sub;
  const token = event.request.challengeAnswer;

  const { state } = await authsignal.validateChallenge({
    action: "cognitoAuth",
    userId,
    token,
  });

  event.response.answerCorrect =
    state === UserActionState.CHALLENGE_SUCCEEDED || state === UserActionState.ALLOW;

  return event;
};
```

## Next steps

* [Adaptive MFA with rules](/actions-rules/rules/getting-started#creating-a-rule-to-challenge-high-risk-users)
* [Using business-specific data points in rules](/actions-rules/rules/custom-data-points)
