Amazon Cognito offers two approaches for authentication: a managed login with a hosted UI, or integration via Custom authentication challenge Lambda triggers when you need more control over the authentication UI and user experience.

To integrate Authsignal with Cognito, you’ll use the second approach. Authsignal offers two ways of integrating with Cognito’s custom authentication challenge Lambda triggers:

  1. Custom UI - Build your own UI using our Client SDKs for web and mobile when you need complete control over the user experience.

Passkey authentication in a native mobile app using Authsignal Client SDKs

  1. Pre-built UI - Drop in our ready-to-use hosted UI that supports passkeys, SMS and WhatsApp OTP, and more. You can customize the design to match your brand.

WhatsApp OTP authentication in a web app using Authsignal's pre-built UI

Lambda integration steps

1. Create auth challenge

The first lambda trigger which we will use is Create auth challenge.

We will use the Authsignal Server SDK in this lambda to return a short-lived challenge token.

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

  // Only required when authenticating with email
  const email = event.request.userAttributes.email;

  // Only required when authenticating with SMS or WhatsApp
  const phoneNumber = event.request.userAttributes.phone_number;

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

  event.response.publicChallengeParameters = {
    token,
  };

  return event;
};

2. Verify auth challenge response

The second lambda trigger which we will use is Verify auth challenge response.

In this lambda we will take the validation token obtained from the Authsignal Client SDK and pass it to the Authsignal Server SDK to verify the challenge.

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

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

  event.response.answerCorrect = isValid;

  return event;
};

App integration steps

1. Obtain a username

Once the user has inputted their email address or phone number you can either:

  • Use this value as the Cognito username, or
  • Use this value to find the user record in your database and obtain their username

The approach will depend on your user pool configuration. For more detail on Cognito usernames refer to the AWS documentation.

Passkeys represent a new paradigm of device-initiated authentication where a username is not required as the first step. For this reason the integration steps are slightly different - see our guide on implementing passkeys with Cognito.

2. Call SignUp (if required)

You can call SignUp either as part of a separate account registration flow, or “just-in-time” before every sign-in attempt (ignoring if the user already exists).

import {
  CognitoIdentityProviderClient,
  SignUpCommand,
} from "@aws-sdk/client-cognito-identity-provider";

const client = new CognitoIdentityProviderClient({
  region: "YOUR_AWS_REGION",
});

// If a password is required, generate a dummy value
// It will never be used for passwordless authentication
const password = Math.random().toString(36).slice(-16) + "X";

const command = new SignUpCommand({
  ClientId: "YOUR_USER_POOL_CLIENT_ID",
  Username: username,
  Password: password,
  UserAttributes: [
    // Include email attribute if obtained in step 1
    {
      Name: "email",
      Value: email,
    },
    // Include phone_number attribute if obtained in step 1
    {
      Name: "phone_number",
      Value: phoneNumber,
    },
  ],
});

await client.send(command);

3. Call InitiateAuth

The next step in authenticating with Cognito from your web or mobile app is to call InitiateAuth. This will invoke the Create auth challenge lambda which we have implemented above to return an Authsignal challenge token.

Here we obtain a challenge token to pass to an Authsignal Client SDK.

import {
  CognitoIdentityProviderClient,
  InitiateAuthCommand,
} from "@aws-sdk/client-cognito-identity-provider";

const client = new CognitoIdentityProviderClient({
  region: "YOUR_AWS_REGION",
});

const command = new InitiateAuthCommand({
  ClientId: "YOUR_USER_POOL_CLIENT_ID",
  AuthFlow: AuthFlowType.CUSTOM_AUTH,
  AuthParameters: {
    USERNAME: username,
  },
});

const output = await client.send(command);

// We will pass this challenge token to an Authsignal Client SDK
const token = output.ChallengeParameters?.token;

4. Use Authsignal

The next step is to use Authsignal to handle presenting the user with a challenge.

Use an Authsignal Client SDK to present the user with a challenge.

Here we show email OTP but our SDKs support a variety of methods including passkeys, SMS or WhatsApp, and authenticator app.

// 1. Set the challenge token obtained from the InitiateAuth call
authsignal.setToken(token);

// 2. Send the user an OTP code via email
await authsignal.email.challenge();

// 3. Verify the code inputted by the user matches the original code
const response = await authsignal.email.verify({ code: "123456" });

// 4. Obtain a validation token for the next step
const validationToken = response.data?.token;

5. Call RespondToAuthChallenge

The final step of your app integration is to call RespondToAuthChallenge. This will invoke the Verify auth challenge response lambda which we have implemented above to complete authentication using our Authsignal validation token.

Pass the validation token obtained from the Authsignal Client SDK to Cognito as the challenge answer.

import {
  CognitoIdentityProviderClient,
  RespondToAuthChallengeCommand,
} from "@aws-sdk/client-cognito-identity-provider";

const client = new CognitoIdentityProviderClient({
  region: "YOUR_AWS_REGION",
});

const command = new RespondToAuthChallengeCommand({
  ClientId: "YOUR_USER_POOL_CLIENT_ID",
  ChallengeName: ChallengeNameType.CUSTOM_CHALLENGE,
  Session: session,
  ChallengeResponses: {
    USERNAME: username,
    ANSWER: token, // The Authsignal validation token
  },
});

const output = await client.send(command);

const accessToken = output.AuthenticationResult?.AccessToken;

Github example code

Next steps