You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AWS Cognito无密码登录如何支持多次验证码输入尝试?

问题:AWS Cognito无密码登录,第二次输入正确验证码提示无效会话

在Ruby on Rails API项目中使用AWS Cognito实现无密码登录,已配置DefineAuthChallenge、CreateAuthChallenge和VerifyAuthChallengeResponse三个Lambda函数,基础功能正常,但存在以下问题:

  • 用户首次输入验证码错误后,第二次输入正确验证码时,Cognito返回**"Invalid session for the user"**错误
  • 查看Lambda日志发现:VerifyAuthChallengeResponse仅在首次尝试时执行,DefineAuthChallenge未记录第二次尝试的会话信息

解决方案

1. 修正DefineAuthChallenge的会话判断逻辑

当前逻辑存在会话状态传递不完整、失败次数判断过早的问题,修改后确保会话能正确延续,且允许指定次数的重试:

def lambda_handler(event:, context:)
  puts "Event: #{event.inspect}"
  if event['triggerSource'] == 'DefineAuthChallenge_Authentication'
    session = event.dig('request', 'session') || []
    puts "Session: #{session.inspect}"

    max_attempts = 3 # 允许最多3次验证码尝试
    failed_attempts = session.count { |attempt| attempt['challengeName'] == 'CUSTOM_CHALLENGE' && !attempt['challengeResult'] }

    if session.empty?
      # 首次登录请求,发起自定义挑战
      puts "New request to log in"
      event['response'] = {
        'challengeName' => 'CUSTOM_CHALLENGE',
        'issueTokens' => false,
        'failAuthentication' => false
      }
    elsif failed_attempts >= max_attempts
      # 超过最大失败次数,拒绝登录
      puts "User has exceeded max failed attempts: #{max_attempts}"
      event['response'] = {
        'issueTokens' => false,
        'failAuthentication' => true
      }
    elsif session.any? { |attempt| attempt['challengeName'] == 'CUSTOM_CHALLENGE' && attempt['challengeResult'] }
      # 存在正确的验证结果,发放令牌
      puts "User correctly entered OTP"
      event['response'] = {
        'issueTokens' => true,
        'failAuthentication' => false
      }
    else
      # 失败次数未超限,继续挑战并传递会话状态
      puts "User incorrectly entered OTP, remaining attempts: #{max_attempts - failed_attempts - 1}"
      event['response'] = {
        'challengeName' => 'CUSTOM_CHALLENGE',
        'issueTokens' => false,
        'failAuthentication' => false,
        'session' => event['request']['session'] # 显式传递会话,确保状态延续
      }
    end
  end

  puts "Response: #{event['response'].inspect}"
  event
end

2. 调整CreateAuthChallenge逻辑,避免重复生成验证码(可选)

原代码每次触发都会生成新验证码,导致用户重试时需要重新接收邮件,修改后可复用未过期的验证码:

require 'aws-sdk-ses'

def lambda_handler(event:, context:)
  puts "Event: #{event.inspect}"

  if event['triggerSource'] == 'CreateAuthChallenge_Authentication'
    session = event.dig('request', 'session') || []
    # 检查会话中是否存在未完成挑战的验证码,存在则复用
    existing_code = session.last&.dig('privateChallengeParameters', 'secretLoginCode') if session.any? && !session.last['challengeResult']
    code = existing_code || generate_code

    # 仅在生成新验证码时发送邮件,避免重复发送
    send_verification_code(event['request']['userAttributes']['email'], code) unless existing_code

    event['response'] = {
      'publicChallengeParameters' => { 'email' => event['request']['userAttributes']['email'] },
      'privateChallengeParameters' => { 'secretLoginCode' => code },
      'challengeMetadata' => 'CODE_DELIVERY'
    }
  end

  puts "Response: #{event['response'].inspect}"
  event
end

def generate_code
  rand(100000..999999).to_s
end

def send_verification_code(email, code)
  ses = Aws::SES::Client.new(region: '*******')
  
  user_status_message = "Welcome!"
  
  ses.send_email({
    destination: { to_addresses: [email] },
    message: {
      body: {
        text: { 
          charset: 'UTF-8', 
          data: "#{user_status_message}\nYour verification code is #{code}" 
        }
      },
      subject: { 
        charset: 'UTF-8', 
        data: 'Your verification code' 
      }
    },
    source: '***@***.***'
  })
end

3. 简化VerifyAuthChallengeResponse逻辑(确保正确性)

无需大幅修改,仅优化结果赋值的简洁性:

def lambda_handler(event:, context:)
  puts "Event: #{event.inspect}"
  
  if event['triggerSource'] == 'VerifyAuthChallengeResponse_Authentication'
    expected_answer = event['request']['privateChallengeParameters']['secretLoginCode']
    provided_answer = event['request']['challengeAnswer']
    
    puts "Expected Answer: #{expected_answer}"
    puts "Provided Answer: #{provided_answer}"
    
    event['response']['answerCorrect'] = provided_answer == expected_answer
    puts event['response']['answerCorrect'] ? "Answer is correct" : "Answer is incorrect"
  end
  
  puts "Response: #{event['response'].inspect}"
  event
end

关键修复说明

  1. 会话状态延续:在DefineAuthChallenge的响应中显式传递session参数,确保Cognito能正确跟踪每一次尝试的状态
  2. 失败次数控制:通过统计会话中的失败记录,精确控制允许的重试次数,避免过早终止会话
  3. 验证码复用:减少不必要的验证码重发,提升用户体验同时降低邮件发送成本

内容的提问来源于stack exchange,提问作者Paco Gómez Capón

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 15:24:53