Custom Auth Challenge
Secure Remote Password (SRP) is a cryptographic protocol enabling password-based authentication without transmitting the password over the network. In Amazon Cognito custom authentication flows, CUSTOM_WITH_SRP incorporates SRP steps for enhanced security, while CUSTOM_WITHOUT_SRP bypasses these for a simpler process. The choice between them depends on your application's security needs and performance requirements. This guide demonstrates how to implement both types of custom authentication flows using AWS Amplify with Lambda triggers.
You can use defineAuth
and defineFunction
to create an auth experience that uses CUSTOM_WITH_SRP
and CUSTOM_WITHOUT_SRP
. This can be accomplished by leveraging Amazon Cognito's feature to define a custom auth challenge and 3 triggers:
To get started, install the aws-lambda
package, which is used to define the handler type.
npm add --save-dev @types/aws-lambda
Create auth challenge trigger
To get started, create the first of the three triggers, create-auth-challenge
. This is the trigger responsible for creating the reCAPTCHA challenge after a password is verified.
import { defineFunction } from "@aws-amplify/backend"
export const createAuthChallenge = defineFunction({ name: "create-auth-challenge",})
After creating the resource file, create the handler with the following contents:
import type { CreateAuthChallengeTriggerHandler } from "aws-lambda";
export const handler: CreateAuthChallengeTriggerHandler = async (event) => { if (event.request.challengeName === "CUSTOM_CHALLENGE") { // Generate a random code for the custom challenge const challengeCode = "123456";
event.response.challengeMetadata = "TOKEN_CHECK";
event.response.publicChallengeParameters = { trigger: "true", code: challengeCode, };
event.response.privateChallengeParameters = { trigger: "true" }; event.response.privateChallengeParameters.answer = challengeCode; } return event;};
Define auth challenge trigger
Next, you will want to create the trigger responsible for defining the auth challenge flow, define-auth-challenge
.
import { defineFunction } from "@aws-amplify/backend"
export const defineAuthChallenge = defineFunction({ name: "define-auth-challenge",})
After creating the resource file, create the handler with the following contents if you are using CUSTOM_WITHOUT_SRP
:
import type { DefineAuthChallengeTriggerHandler } from "aws-lambda"
export const handler: DefineAuthChallengeTriggerHandler = async (event) => { // Check if this is the first authentication attempt if (event.request.session.length === 0) { // For the first attempt, we start with the custom challenge event.response.issueTokens = false; event.response.failAuthentication = false; event.response.challengeName = "CUSTOM_CHALLENGE"; } else if ( event.request.session.length === 1 && event.request.session[0].challengeName === "CUSTOM_CHALLENGE" && event.request.session[0].challengeResult === true ) { // If this is the second attempt (session length 1), // it was a CUSTOM_CHALLENGE, and the result was successful event.response.issueTokens = true; event.response.failAuthentication = false; } else { // If we reach here, it means either: // 1. The custom challenge failed // 2. We've gone through more attempts than expected // In either case, we fail the authentication event.response.issueTokens = false; event.response.failAuthentication = true; }
return event;};
Or if you are using CUSTOM_WITH_SRP
:
import type { DefineAuthChallengeTriggerHandler } from "aws-lambda"
export const handler: DefineAuthChallengeTriggerHandler = async (event) => { // First attempt: Start with SRP_A (Secure Remote Password protocol, step A) if (event.request.session.length === 0) { event.response.issueTokens = false; event.response.failAuthentication = false; event.response.challengeName = "SRP_A"; } else if ( event.request.session.length === 1 && event.request.session[0].challengeName === "SRP_A" && event.request.session[0].challengeResult === true ) { // Second attempt: SRP_A was successful, move to PASSWORD_VERIFIER event.response.issueTokens = false; event.response.failAuthentication = false; event.response.challengeName = "PASSWORD_VERIFIER"; } else if ( event.request.session.length === 2 && event.request.session[1].challengeName === "PASSWORD_VERIFIER" && event.request.session[1].challengeResult === true ) { // Third attempt: PASSWORD_VERIFIER was successful, move to CUSTOM_CHALLENGE event.response.issueTokens = false; event.response.failAuthentication = false; event.response.challengeName = "CUSTOM_CHALLENGE"; } else if ( event.request.session.length === 3 && event.request.session[2].challengeName === "CUSTOM_CHALLENGE" && event.request.session[2].challengeResult === true ) { // Fourth attempt: CUSTOM_CHALLENGE was successful, authentication complete event.response.issueTokens = true; event.response.failAuthentication = false; } else { // If we reach here, it means one of the challenges failed or // we've gone through more attempts than expected event.response.issueTokens = false; event.response.failAuthentication = true; }
return event;};
Verify auth challenge response trigger
Lastly, create the trigger responsible for verifying the challenge response. For the purpose of this example, the verification check will always return true.
import { defineFunction, secret } from "@aws-amplify/backend"
export const verifyAuthChallengeResponse = defineFunction({ name: "verify-auth-challenge-response",})
After creating the resource file, create the handler with the following contents:
import type { VerifyAuthChallengeResponseTriggerHandler } from "aws-lambda"
export const handler: VerifyAuthChallengeResponseTriggerHandler = async ( event) => { event.response.answerCorrect = true; return event;};
Configure auth resource
Finally, import and set the three triggers on your auth resource:
import { defineAuth } from "@aws-amplify/backend"import { createAuthChallenge } from "./create-auth-challenge/resource"import { defineAuthChallenge } from "./define-auth-challenge/resource"import { verifyAuthChallengeResponse } from "./verify-auth-challenge-response/resource"
/** * Define and configure your auth resource * @see https://docs.amplify.aws/gen2/build-a-backend/auth */export const auth = defineAuth({ loginWith: { email: true, }, triggers: { createAuthChallenge, defineAuthChallenge, verifyAuthChallengeResponse, },})
After deploying the changes, whenever a user attempts to sign in with CUSTOM_WITH_SRP
or CUSTOM_WITHOUT_SRP
, the Lambda challenges will be triggered.