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 app.

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

  • InitiateAuth
  • RespondToAuthChallenge
  • SignUp

Example code

You can find the full code example 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):

yarn install

Running the SPA

VITE_COGNITO_CLIENT_ID=<ID_HERE> yarn run dev

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

Enable self-registration so we can let users sign up directly from the SPA without going through a backend.

Step 4

Disable the Cognito Hosted UI. The SPA replaces this.

Step 5

Select Public client for the app type. Also select `Don’t 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({
    action,
    userId,
    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({
    action,
    userId,
    token,
  });

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

  return event;
};

Important API calls

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

InitiateAuth

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.

ResponseToAuthChallenge

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.

SignUp

This API call is responsible for adding a user to the user pool. In this example we call SignUp if the sign in flow fails. This API is passed a username and password. Because we’ve configured a passwordless authentication flow the user doesn’t need to be aware of the password, so we generate a secure random password in the client and use that.

Next steps