0

我的目标是通过向用户手机发送短信来实现 otp。我能够使用 cognito 自定义身份验证流程来实现这一点,但是,只有当用户在第一次尝试中成功时才有效,如果用户输入错误的代码,会话将过期并且需要再次发送新代码,糟糕的 ux . 我确实需要至少 3 次尝试,理论上这是整个 cognito auth 流程中的 3 次会话。

我将分享我用于此的四个认知 lambda(认知触发器):preSignUpdefineAuthChallengecreateAuthChallengeverifyChanllenge

// preSignUp lambda
exports.handler = async (event) => {
    event.response.autoConfirmUser = true;
    event.response.autoVerifyPhone = true;
    return event;
};
// defineAuthChallenge
exports.handler = async (event, context, callback) => {    
    if (event.request.session.length >= 3 && event.request.session.slice(-1)[0].challengeResult === false) {
        // wrong OTP even After 3 sessions? FINISH auth, dont send token
        event.response.issueToken = false;
        event.response.failAuthentication = true;
    } else if (event.request.session.length > 0 && event.request.session.slice(-1)[0].challengeResult === true) { 
        // Last answer was Correct! send token and FINISH auth
        event.response.issueTokens = true;
        event.response.failAuthentication = false;
    } else { 
        // INIT flow - OR - not yet received correct OTP
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'CUSTOM_CHALLENGE';
    }
    return event;
};
// createAuthChallenge
exports.handler = async (event, context) => {
    if (!event.request.session || event.request.session.length === 0) {
        // create only once the otp, send over sms only once
        var otp = generateOtp();
        const phone = event.request.userAttributes.phone_number;
        sendSMS(phone, otp);
    } else {
        // get previous challenge answer
        const previousChallenge = event.request.session.slice(-1)[0];
        otp = previousChallenge.challengeMetadata;
    }
    event.response = {
        ...event.response, 
        privateChallengeParameters: {
           answer: otp
        },
        challengeMetadata: otp // save it here to use across sessions
    };
    return event
}
// verifyChanllenge
exports.handler = async (event, context) => {
  event.response.answerCorrect = event.request.privateChallengeParameters.answer === event.request.challengeAnswer;
  return event
}

对于客户端,这是一个 RN 应用程序,我正在使用放大,这是应用程序中的流程:

// SignIn form screen
import { Auth } from "aws-amplify";

const signUp = (phone) => {
    Auth.signUp({
      username: phone,
      /** dummy pass since its required but unused for OTP */
      password: "12345678"
    }).then(() => {
      // after signup, go an automatically login (which trigger sms to be sent)
      otpSignIn(phone);
    }).catch(({code}) => {
      // signup fail because user already exists, ok, just try login it
      if (code === SignUpErrCode.USER_EXISTS) {
        otpSignIn(phone)
      } else {
        ...  
      }
    })
  }

const otpSignIn = async (phoneNumber) => {
    const cognitoUser = await Auth.signIn(phoneNumber)
    setCognitoUser(cognitoUser);
    navigate("ConfirmNumber", {phoneNumber});
  }
import { Auth } from "aws-amplify";
let cognitoUser;

export function setCognitoUser(user) {
  console.log('setCognitoUser', user)
  cognitoUser = user;
}

export function sendChallenge(challengeResponse) {
  return Auth.sendCustomChallengeAnswer(cognitoUser, challengeResponse)
}
// Confirm number screen
const onChangeText = (value) => {
    if (value.length === 4) {
      try {
        const user = await sendChallenge(value)
        // WEIRD THING NUMBER 1
        // when the user send the second attempt, no error is raised, this promise is resolve! 
        // even when the trigger *verifyChanllenge* is returning false.

      } catch (err) {
        // WEIRD THING NUMBER 2
        // from the trigger *createAuthChallenge* if i define the anser in the if block, 
        // and not store such answer for future use (i do that in else block), then, 
        // for the second..third attempt the error raised here is that *Invalid session for user* which mean session has expired, 
        // what i need is to persist session until third attempt 
      }
    }
}
// this is amplify config: try 1
const awsExports = {
  Auth: {
    region: ...,
    userPoolId: ...,
    userPoolWebClientId: ...,
    authenticationFlowType: 'CUSTOM_AUTH',
  },
  ...
}
Amplify.configure(awsExports);  
// this is amplify config: try 2
import {Auth} from "aws-amplify"
Auth.configure({
  authenticationFlowType: 'CUSTOM_AUTH'
});
4

1 回答 1

1

上面代码中的一切都是正确的,authenticationFlowType: 'CUSTOM_AUTH'不需要进行放大的配置。

问题是当触发器defineAuthChallenge设置此组合Auth.sendCustomChallengeAnswer(cognitoUser, challengeResponse)时不会引发错误:

event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';

这提出了下一次尝试。

所以我找到了一种在用户失败 otp 时检查错误的方法:

const sendCode = async (value) => {
    try {
      // send the answer to the User Pool
      // this will throw an error if it's the 3rd wrong answer
      const user = await sendChallenge(value);

      // the answer was sent successfully, but it doesnt mean it is the right one
      // so we should test if the user is authenticated now
      // this will throw an error if the user is not yet authenticated:
      await Auth.currentSession();
    } catch (err) {
      setError(true);
    }
  }
于 2021-08-11T23:47:21.810 回答