This guide will demonstrate how to use the pre-built UI in redirect mode to present a passwordless challenge to the user on login.

It shows how to use the Authsignal Node SDK in your Cognito lambdas as well as the Authsignal Web SDK in your web app.

The web app is a SPA which makes requests to a Node/Express backend. The backend uses the following commands from the @aws-sdk/client-cognito-identity-provider package to talk to Cognito:

  • AdminInitiateAuth
  • AdminRespondToAuthChallenge
  • AdminCreateUser
  • AdminSetUserPassword

Example code

You can find the full example code on Github.

User flow

1

The user enters their email to sign in.

2

The user is redirected to Authsignal to complete a challenge.

3

The user is redirected back to the client which then fetches an access token from Cognito.

Sequence diagram

Installation

Install the required npm modules with yarn (or npm) in both the SPA and backend folders:

yarn install

Running the SPA

yarn run dev

Running the backend

COGNITO_CLIENT_ID=<ID_HERE> COGNITO_CLIENT_SECRET=<SECRET_HERE> COGNITO_USER_POOL_ID=<USER_POOL_ID_HERE> node app.js

User pool settings

For this example there are several important settings to configure when creating a Cognito User Pool:

Step 1

Choose Email as the sign-in option.

Step 2

Choose No MFA as the MFA option. This can be implemented with Authsignal instead.

Step 3

Disable self-registration because we are creating users from our backend.

Step 4

Disable the Cognito Hosted UI. The SPA replaces this.

Step 5

Select Confidential client for the app type. Also select Generate a client secret.

The lambdas

The example repo contains four lambdas which can be deployed to your AWS environment.

Once deployed, these lambdas can be connected to your Cognito user pool:

Create Auth Challenge lambda

This lambda uses the Authsignal Node.js SDK to return a short-lived token back to the app which can be passed to the Authsignal Web SDK to launch the Authsignal pre-built UI in a popup:

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

  // This can be any value which defines your login action
  const action = "cognitoAuth";

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

  event.response.publicChallengeParameters = { url };

  return event;
};

Verify Auth Challenge Response lambda

This lambda takes the result token returned by the Authsignal Web SDK and passes it to the Authsignal Node.js SDK to validate the result of the challenge:

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

  // This must be the same value used in the previous step
  const action = "cognitoAuth";

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

  event.response.answerCorrect = state === "CHALLENGE_SUCCEEDED";

  return event;
};

Important API calls

The SPA makes use of 4 Cognito API calls via the AWS SDK:

AdminInitiateAuth

This API call is responsible for starting the authentication process. This API is passed the username and CUSTOM_AUTH as the authentication flow and returns a URL to Authsignal’s pre-built UI as well as a Cognito session token. The SPA then redirects to the Authsignal URL which will allow the user to complete a passwordless authentication challenge. After the challenge is completed the pre-built UI will redirect back to the SPA’s /callback endpoint with a token in the query string.

AdminRespondToAuthChallenge

This API call is responsible for completing the authentication flow. This API is passed multiple parameters, most importantly the session string, and the Authsignal token as the ANSWER parameter. The return values include both a cognito access and refresh token.

AdminCreateUser

This API call is responsible for adding a user to the user pool. In this example we first check if a user exists and if they don’t we call this API. This API is passed a variety of parameters including the email address, which is passed as both the username and email.

AdminSetUserPassword

This API call is responsible for setting the user’s password. This API is passed a username and password. We also pass a parameter to make the password permanent rather than temporary. Because the user does not need to be aware of their password, we randomly generate a secure password and use that instead of getting one from the user.

Next steps