In the previous guide we covered the integration steps required to sign in to Cognito with an email or phone number - either using an Authsignal Client SDK or the Authsignal pre-built UI.

This guide will demonstrate how to use an Authsignal Client SDK to let the user create a passkey as an alternative authentication option which they can use for future sign-ins.

This guide shows how to use passkeys with our React Native SDK, but you can just as easily use any of our Mobile SDKs for Swift, Kotlin, or Flutter, or our Web SDK for browser-based apps.

Github example code

Creating a passkey

Once a user has signed in using their email address or phone number, we can prompt them to create a passkey to use for next time.

Prompting the user to create a passkey after sign-in

Lambda integration

To authorize binding the passkey to the user, we will create an authenticated endpoint which our app can call to obtain an Authsignal token.

This endpoint will use a JWT Authorizer to authenticate using the Cognito access token.

export const handler = async (event: APIGatewayProxyEventV2WithJWTAuthorizer) => {
  const claims = event.requestContext.authorizer.jwt.claims;
  const userId = claims.sub;

  const { token: authsignalToken } = await authsignal.track({
    userId,
    action: "addAuthenticator",
    attributes: {
      scope: "add:authenticators",
    },
  });

  return {
    authsignalToken,
  };
};

The value we provide for action here will be used to keep track of these events in the Authsignal Portal.

App integration

  1. Call our endpoint to obtain a short-lived Authsignal token.
// Replace with your API endpoint URL
const url = "https://abcd1234.execute-api.us-west-1.amazonaws.com/authenticators";

const authsignalToken = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${cognitoAccessToken}`,
  },
})
  .then((res) => res.json())
  .then((json) => json.authsignalToken);
  1. Set the Authsignal token using the React Native SDK.
await authsignal.setToken(authsignalToken);
  1. Create the passkey using the React Native SDK.
// This can be the Cognito username (e.g email or phone number)
const username = "jane@authsignal.com";

// This is an optional display name for the passkey
const displayName = "Jane Smith";

await authsignal.passkey.signUp({ username, displayName });

Signing in with a passkey

Now that the user has created a passkey, the next time they sign in we can present them with the option to use their passkey.

Using a passkey to sign in

Lambda integration

We need to make a small change to our Create auth challenge lambda to ensure that passkey sign-ins are correctly attributed to the cognitoAuth action when we view it in the Authsignal Portal.

// Lookup any pending passkey challenge
const { challengeId } = await authsignal.getChallenge({
  userId,
  verificationMethod: VerificationMethod.PASSKEY,
});

// If one exists, bind it to our 'cognitoAuth' action
const { token, isEnrolled } = await authsignal.track({
  action: "cognitoAuth",
  userId,
  attributes: {
    email,
    phoneNumber,
    challengeId,
  },
});

App integration

The final step is to add some code to our React Native app’s sign-in screen.

We’ll write some code to automatically show the passkey sign-in prompt if the user has one available on their device.

async function signInWithPasskey() {
  // Show the passkey sign-in prompt
  const { data, errorCode } = await authsignal.passkey.signIn({
    action: "cognitoAuth",
  });

  // Exit if the user has no passkey available or if they dismissed the prompt
  if (errorCode === ErrorCode.user_canceled || errorCode === ErrorCode.no_credential) {
    return;
  }

  // Get the Cognito username associated with the passkey
  const username = data.username;

  // Get the Authsignal validation token
  const token = data.token;

  // Call InitiateAuth to obtain a session
  const initiateAuthCommand = new InitiateAuthCommand({
    ClientId: "YOUR_USER_POOL_CLIENT_ID",
    AuthFlow: AuthFlowType.CUSTOM_AUTH,
    AuthParameters: {
      USERNAME: username,
    },
  });

  const initiateAuthOutput = await client.send(initiateAuthCommand);

  const session = initiateAuthOutput.Session;

  // Call RespondToAuthChallenge and pass the Authsignal validation token
  const respondToAuthChallengeCommand = new RespondToAuthChallengeCommand({
    ClientId: "YOUR_USER_POOL_CLIENT_ID",
    ChallengeName: ChallengeNameType.CUSTOM_CHALLENGE,
    Session: session,
    ChallengeResponses: {
      USERNAME: username,
      ANSWER: token,
    },
  });

  const respondToAuthChallengeOutput = await cognito.send(initiateAuthCommand);

  // Obtain the Cognito access token and complete sign-in
  const accessToken = respondToAuthChallengeOutput.AuthenticationResult?.AccessToken;
}

Then finally we’ll run this function in a useEffect hook when our sign-in screen first appears.

useEffect(() => {
  signInWithPasskey();
}, []);

Next steps