ツー

日常の記録

Cognito User Poolのワンタイムパスワードを試してみたができなかった

ここらへんを参考にしてワンタイムパスワードを実装しようとしたができなかったのでメモっておく。

ソースは末尾に。

出力結果

まずは admin-create-user -> admin-set-user-password -> admin-initiate-auth の順で実行。

$ aws cognito-idp admin-initiate-auth --user-pool-id ap-northeast-1_xxxxxxxxxx --auth-flow CUSTOM_AUTH --client-id yyyyyyyyyy --auth-parameters USERNAME=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee

An error occurred (InvalidLambdaResponseException) when calling the AdminInitiateAuth operation: Invalid lambda function output : Invalid JSON

adminだからダメなのかなと思って、 admin-create-user -> admin-set-user-password -> initiate-auth の順で実行。

$ aws cognito-idp initiate-auth --auth-flow CUSTOM_AUTH --client-id yyyyyyyyyy --auth-parameters USERNAME=test@example.com

An error occurred (InvalidLambdaResponseException) when calling the InitiateAuth operation: Invalid lambda function output : Invalid JSON

エラーの内容が同じだったので根本的に何かが違う気がするがわからん。

VerifyAuthChallengeResponse でこけてたけど、データをログに出しても大丈夫な感じはするんだけど、うーーーん。

{
  version: '1',
  region: 'ap-northeast-1',
  userPoolId: 'ap-northeast-1_xxxxxxxx',
  userName: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
  callerContext: {
    awsSdkVersion: 'aws-sdk-unknown-unknown',
    clientId: 'yyyyyyyyyy'
  },
  triggerSource: 'DefineAuthChallenge_Authentication',
  request: {
    userAttributes: {
      sub: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
      'cognito:user_status': 'CONFIRMED',
      email: 'test@example.com'
    },
    session: []
  },
  response: {
    challengeName: 'CUSTOM_CHALLENGE',
    issueTokens: false,
    failAuthentication: false
  }
}

ソース

serverless frameworkです。ほぼコピペ。

service: test
frameworkVersion: '2'

provider:
  name: aws
  runtime: nodejs12.x
  lambdaHashingVersion: 20201221
  stage: dev
  region: ap-northeast-1

functions:
  define:
    handler: handler.define
  create:
    handler: handler.create
  verify:
    handler: handler.verify

resources:
  Resources:
    UserPool:
      Type: AWS::Cognito::UserPool
      Properties:
        UserPoolName: test
        UsernameAttributes: [email]
        LambdaConfig:
          CreateAuthChallenge: !GetAtt CreateLambdaFunction.Arn
          DefineAuthChallenge: !GetAtt DefineLambdaFunction.Arn
          VerifyAuthChallengeResponse: !GetAtt VerifyLambdaFunction.Arn

    UserPoolClient:
      Type: AWS::Cognito::UserPoolClient
      Properties:
        UserPoolId: !Ref UserPool
        ClientName: test
        GenerateSecret: false

    CreatePermission:
      Type: 'AWS::Lambda::Permission'
      Properties:
        Action: lambda:InvokeFunction
        FunctionName: !GetAtt CreateLambdaFunction.Arn
        Principal: "cognito-idp.amazonaws.com"
        SourceArn: !Join [ "", [ "arn:aws:cognito-idp", ":", Ref: "AWS::Region", ":", Ref: "AWS::AccountId", ":", "userpool/", !Ref UserPool ] ]
    
    DefinePermission:
      Type: 'AWS::Lambda::Permission'
      Properties:
        Action: lambda:InvokeFunction
        FunctionName: !GetAtt DefineLambdaFunction.Arn
        Principal: "cognito-idp.amazonaws.com"
        SourceArn: !Join [ "", [ "arn:aws:cognito-idp", ":", Ref: "AWS::Region", ":", Ref: "AWS::AccountId", ":", "userpool/", !Ref UserPool ] ]
    
    VerifyPermission:
      Type: 'AWS::Lambda::Permission'
      Properties:
        Action: lambda:InvokeFunction
        FunctionName: !GetAtt VerifyLambdaFunction.Arn
        Principal: "cognito-idp.amazonaws.com"
        SourceArn: !Join [ "", [ "arn:aws:cognito-idp", ":", Ref: "AWS::Region", ":", Ref: "AWS::AccountId", ":", "userpool/", !Ref UserPool ] ]
const { randomDigits } = require('crypto-secure-random-digit');

module.exports.verify = (event) => {
  const expectedAnswer = event.request.privateChallengeParameters.secretLoginCode; 
  if (event.request.challengeAnswer === expectedAnswer) {
      event.response.answerCorrect = true;
  } else {
      event.response.answerCorrect = false;
  }

  return event;
};

module.exports.create = (event, context, callback) => {
    let secretLoginCode = null;
    if (!event.request.session || !event.request.session.length) {
        secretLoginCode = randomDigits(6).join('');
        console.log(secretLoginCode);

    } else {
        const previousChallenge = event.request.session.slice(-1)[0];
        secretLoginCode = previousChallenge.challengeMetadata.match(/CODE-(\d*)/)[1];
    }

    event.response.publicChallengeParameters = { email: event.request.userAttributes.email };
    event.response.privateChallengeParameters = { secretLoginCode };
    event.response.challengeMetadata = `CODE-${secretLoginCode}`;
    return event;
};

module.exports.define = (event, context, callback) => {
    if (event.request.session &&
        event.request.session.length >= 3 &&
        event.request.session.slice(-1)[0].challengeResult === false) {
        // ユーザの入力コードが3回間違っていた場合(認証失敗)
        event.response.issueTokens = false;
        event.response.failAuthentication = true;
    } else if (event.request.session &&
        event.request.session.length &&
        event.request.session.slice(-1)[0].challengeResult === true) {
        // ユーザの入力コードが正しい場合(認証成功)
        event.response.issueTokens = true;
        event.response.failAuthentication = false;
    } else {
        // それ以外: ユーザの入力コードが正しくなく、3回間違えてない場合 (認証チャレンジ継続)
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'CUSTOM_CHALLENGE';
    }

    console.log(event);
    return event;
};