AWS | Setup Lambda Triggers for Authentication

Custom Authentication flow works with the help of Lambda Triggers which act as handlers for different events during Users Sign in process.

Refer to the Steps below to setup & configure Lambda Triggers.


STEP 1: #

  1. Sign in to the AWS Console, Navigate/Search for Lambda, and Click on “Lambda“.

STEP 2: #

  1. Open AWS Lambda console
  2. Click on Functions tab
  3. Make sure the correct Region is selected at the top right corner.
  4. Click on Create Function to create the following 4 functions one after another:

autoConfimUserFN, defineAuthChallengeFN, createAuthChallengeFN, verifyAuthChallengeFN

Please refer to the next steps to start creating.

STEP 3: [NOT CORRECT] #

Create function: autoConfirmUserFN

  1. Select “Author from Scratch
  2. Set the function name as “autoConfirmUserFN
  3. Select “Node.js 16.x” as Runtime. Nodeeeeeeee VErsssionn 18.x also rename inex.mjs to index.js
  4. Select Architecture “x86_64
  5. Click on “Create functionand refer to the next step

Paste the following code as shown below:

exports.handler = async function (event) {
event.response.autoConfirmUser = true;
// Set the SMS as verified if it is in the request
if (event.request.userAttributes.hasOwnProperty('phone_number')) {
        if(event.request.userAttributes.PhoneNumber===''){
    }else{
event.response.autoVerifyPhone = true;
event.response.autoVerifyEmail = false;  
    }

}
// Set the email as verified if it is in the request
if (event.request.userAttributes.hasOwnProperty('email')) {
    if(event.request.userAttributes.email===''){
    }else{
     event.response.autoVerifyEmail = true;
event.response.autoVerifyPhone = false;   
    }

}
return event;
};

Click on “Deploy” to complete function creation.

STEP 4: #

Create function: defineAuthChallengeJS

  1. Select “Author from Scratch
  2. Set the function name as “defineAuthChallengeJS
  3. Select “Node.js 16.x” as Runtime
  4. Select Architecture “x86_64
  5. Click on “Create functionand refer to the next step

Paste the following code as shown below:

exports.handler = async function (event) {
'use strict';

exports.handler = async (event) => {
    console.log('RECEIVED------  event: ', JSON.stringify(event, null, 2));
   
    // The first auth request for CUSTOM_CHALLENGE from the AWSMobileClient (in iOS native app) actually comes in as an "SRP_A" challenge (BUG in AWS iOS SDK), so swicth to CUSTOM_CHALLENGE and clear session.
    if (event.request.session && event.request.session.length && event.request.session.slice(-1)[0].challengeName == "SRP_A") {
        console.log('New CUSTOM_CHALLENGE', JSON.stringify(event, null, 2));
        event.request.session = []; 
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'CUSTOM_CHALLENGE';
    } 
    // User successfully answered the challenge, succeed with auth and issue OpenID tokens
    else if (event.request.session &&
        event.request.session.length &&
        event.request.session.slice(-1)[0].challengeName === 'CUSTOM_CHALLENGE' &&
        event.request.session.slice(-1)[0].challengeResult === true) {
        
        console.log('The user provided the right answer to the challenge; succeed auth');
        event.response.issueTokens = true;
        event.response.failAuthentication = false;
    }
    // After 3 failed challenge responses from user, fail authentication
    else if (event.request.session && 
    // event.request.session.length >= 1 && 
    event.request.session.slice(-1)[0].challengeResult === false) {
        console.log('FAILED Authentication: The user provided a wrong answer 3 times');
        event.response.issueTokens = false;
        event.response.failAuthentication = true;
    } 
    // The user did not provide a correct answer yet; present CUSTOM_CHALLENGE again
    else {
        console.log('User response incorrect: Attempt [' + event.request.session.length + ']');
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'CUSTOM_CHALLENGE';
    }
    
    console.log('RETURNED event: ', JSON.stringify(event, null, 2));
    
    return event;

Click on “Deploy” to complete function creation.

STEP 5: #

Create function: createAuthChallengeFN

  1. Select “Author from Scratch
  2. Set the function name as “createAuthChallengeFN
  3. Select “Node.js 16.x” as Runtime
  4. Select Architecture “x86_64
  5. Select “Use an existing role” under Execution role {IMPORTANT}
  6. Choose previously created “smsEmailSendingRole” as the IAM role created using this GUIDE {IMPORTANT}
  7. Click on “Create functionand refer to the next step

Paste the following code as shown below:

const AWS = require("aws-sdk");
 
 var sns = new AWS.SNS();
 
 function splitStr(str) {
    // Function to split string
    let string = str.split("-|-");
    return string; 
}

function generateRandomNumber() {
//  var minm = 1000;  four digit otp
// var maxm = 9999;
    var minm = 100000;
    var maxm = 999999;
    return Math.floor(Math.random() * (maxm - minm + 1)) + minm;
}

  // Send secret code over SMS via Amazon Simple Notification Service (SNS)
 async function sendSMSviaSNS(phoneNumber, secretLoginCode, subject, bodyString, footerString1, fromEmail, companyLogoURL, appName,footerString2, bodyHeading) {
 var mssg= `${appName} ${bodyHeading} : ${secretLoginCode}\n\n\n${footerString1}`  ;
 const params = { "Message": mssg, "PhoneNumber": phoneNumber };
     const response =await sns.publish(params).promise();
 return response;
 }
 
 
   // Send secret code over Email via Amazon SES
  async function sendSMSviaSES(email, secretLoginCode, subject, bodyString, footerString1, fromEmail, companyLogoURL, appName,footerString2, bodyHeading) {
 const client = new AWS.SES();
var htmlString = `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title></title>
  <!--[if mso]><style type="text/css">body, table, td, a { font-family: Arial, Helvetica, sans-serif !important; }</style><![endif]-->
</head>

<body style="font-family: Helvetica, Arial, sans-serif; margin: 0px; padding: 0px; background-color: #ffffff;">
  <table role="presentation"
    style="width: 100%; border-collapse: collapse; border: 0px; border-spacing: 0px; font-family: Arial, Helvetica, sans-serif; background-color: rgb(239, 239, 239);">
    <tbody>
      <tr>
        <td align="center" style="padding: 1rem 2rem; vertical-align: top; width: 100%;">
          <table role="presentation" style="max-width: 600px; border-collapse: collapse; border: 0px; border-spacing: 0px; text-align: left;">
            <tbody>
              <tr>
                <td style="padding: 40px 0px 0px;">
                  <div style="text-align: left;">
                    <div style="padding-bottom: 20px;"><img src="${companyLogoURL}" alt="${appName}" style="width: 56px;"></div>
                  </div>
                  <div style="padding: 20px; background-color: rgb(255, 255, 255);">
                    <div style="color: rgb(0, 0, 0); text-align: left;">
                      <h1 style="margin: 1rem 0">${bodyHeading}</h1>
                      <p style="padding-bottom: 16px">${bodyString}</p>
                      <p style="padding-bottom: 16px"><strong style="font-size: 190%; color:SlateBlue;">${secretLoginCode}</strong></p>
                      <p style="padding-bottom: 12px;font-size: 80%;">${footerString1}</p>
                 
                    </div>
                  </div>
                  <div style="padding-top: 20px; color: rgb(153, 153, 153); text-align: center;">
                    <p style="padding-bottom: 16px">${footerString2}</p>
                  </div>
                </td>
              </tr>
            </tbody>
          </table>
        </td>
      </tr>
    </tbody>
  </table>
</body>
</html>
`;

    const body =  `${bodyString} ${secretLoginCode}`  ;
    const message = {
        "Subject": {"Data": subject},
        "Body": {"Html": {"Data": htmlString}}
    };
    const response = await client.sendEmail({
        Source: `${appName} <${fromEmail}>`  ,
        Destination: {"ToAddresses": [email]},
        Message: message
    }).promise();
    
    return response;

 }
 
 
 
 

 
 
 
 
 
 


 // Main handler
exports.handler = async function (event) {

 let secretLoginCode;
 
if (event.request.challengeName === 'CUSTOM_CHALLENGE' &&  event.request.session.length <2) {
 
 var phoneNumber = event.request.userAttributes.phone_number;
  var email = event.request.userAttributes.email;
    var metaData = splitStr(event.request.userAttributes.website);
   // var resData ='New CUSTOM_CHALLENGE'+ JSON.stringify(event.response, null, 2);
   // console.log(resData);
if (event.request.userAttributes.hasOwnProperty('phone_number')) {
   // Phone Number verify
     //--------  uncomment line below for test otp --------
//  secretLoginCode = "000000";
 
  //-------- uncomment line below for Real otp --------
   secretLoginCode = generateRandomNumber();
  await sendSMSviaSNS(phoneNumber, secretLoginCode,metaData[0],metaData[1],metaData[2],metaData[3],metaData[4],metaData[5],metaData[6],metaData[7] ); // use SNS for sending SMS,
 
  }else if (event.request.userAttributes.hasOwnProperty('email')) {
   // Email Verify
        //--------  uncomment line below for test otp --------
//  secretLoginCode = "111111";
 
  //-------- uncomment line below for Real otp --------
   secretLoginCode = generateRandomNumber();
  await sendSMSviaSES(email, secretLoginCode,metaData[0],metaData[1],metaData[2],metaData[3],metaData[4],metaData[5],metaData[6],metaData[7] ); // use SNS for sending SMS,

  }else{
   // ignore
  }

 event.response.privateChallengeParameters = {};
 event.response.privateChallengeParameters.answer = secretLoginCode;
   
 }
 else {
 //  const expectedAnswer = event.request.privateChallengeParameters.answer;
 // if (event.request.challengeAnswer == expectedAnswer) {
 const previousChallenge = event.request.session.slice(-1)[0];
 // secretLoginCode = previousChallenge.challengeMetadata.match(/CODE-(\d*)/)[1];
  event.response.privateChallengeParameters = {};
    event.response.privateChallengeParameters.answer = secretLoginCode;
    // event.response.publicChallengeParameters = {
    //   // hint: 'Enter the secret code',
    //   phone: event.request.userAttributes.phone_number 
    // };
 }
 
event.response.challengeMetadata = `CODE-${secretLoginCode}` ;

 return event;
};


Click on “Deploy” to complete function creation.

STEP 6: #

Create function: verifyAuthChallengeFN

  1. Select “Author from Scratch
  2. Set the function name as “verifyAuthChallengeFN
  3. Select “Node.js 16.x” as Runtime
  4. Select Architecture “x86_64
  5. Click on “Create functionand refer to the next step

Paste the following code as shown below:

exports.handler = async function (event) {
const expectedAnswer = event.request.privateChallengeParameters.answer;
 if (event.request.challengeAnswer == expectedAnswer) {
 event.response.answerCorrect = true;
 }
 else {
 event.response.answerCorrect = false;
 }
 return event;
};

Click on “Deploy” to complete function creation.

STEP 7: #

All our 4 newly created Lambda Functions can be seen here:


STEP 8: [NOT CORRECT] from here… #

Link Lambda Triggers with the Custom Authentication Pool

Navigate to AWS Console –> Amazon Cognito –> User pools –> “CustomAuthenticationPool“, click on it to view its details

STEP 9: #

  1. Click on “User pool properties” tab
  2. Click on “Add Lambda trigger” to add the 4 lambda functions created in previous steps (autoConfimUserFN, defineAuthChallengeFN, createAuthChallengeFN, verifyAuthChallengeFN)

STEP 10: #

Add autoConfimUserFN function as a Lambda trigger.

  1. Select “Sign-up” as Trigger type
  2. Select “Pre sign-up trigger” under Sign-up category
  3. Select “autoConfimUserFN ” from the function list

Click “Add Lambda trigger” to successfully add this function as lambda handler.

STEP 11: #

Add defineAuthChallengeFN function as a Lambda trigger.

  1. Select “Custom authentication” as Trigger type
  2. Select “Define auth challenge” under Custom authentication category
  3. Select “defineAuthChallengeFN” from the function list

Click “Add Lambda trigger” to successfully add this function as a lambda handler.

STEP 12: #

Add createAuthChallengeFN function as a Lambda trigger.

  1. Select “Custom authentication” as Trigger type
  2. Select “Create auth challenge” under Custom authentication category
  3. Select “createAuthChallengeFN” from the function list

Click “Add Lambda trigger” to successfully add this function as a lambda handler.

STEP 13: #

Add verifyAuthChallengeFN function as a Lambda trigger.

  1. Select “Custom authentication” as Trigger type
  2. Select “Verify auth challenge response” under Custom authentication category
  3. Select “verifyAuthChallengeFN” from the function list

Click “Add Lambda trigger” to successfully add this function as lambda handler.

STEP 14: #

👏 Congratulations! We have successfully completed the Lamda trigger setup process.

All the created Lambda triggers can be seen here for the “CustomAuthenticationPool“.

Powered by BetterDocs