AWS SDK를 사용해 Lambda 함수로 알려진 Amazon Cognito 사용자 자동 확인하기

AWS SDK를 사용해 Lambda 함수로 알려진 Amazon Cognito 사용자 자동 확인하기 (Automatically confirm known Amazon Cognito users with a Lambda function using an AWS SDK)

다음 코드 예제들은 Lambda 함수로 알려진 Amazon Cognito 사용자를 자동 확인하는 방법을 보여줘요.

전체 흐름은 다음과 같아요.

  1. 사용자 풀(user pool)이 PreSignUp 트리거에 대해 Lambda 함수를 호출하도록 구성합니다.
  2. Amazon Cognito로 사용자를 가입시킵니다.
  3. Lambda 함수가 DynamoDB 테이블을 검색해 알려진 사용자를 자동 확인합니다.
  4. 새 사용자로 로그인한 뒤 리소스를 정리합니다.

출처: AWS Lambda 개발자 안내서

본문

Go — SDK for Go V2

참고

이 예제의 전체 코드와 설정·실행 방법은 AWS Code Examples Repository(GitHub)에서 볼 수 있어요.

대화형 시나리오를 명령 프롬프트에서 실행

AutoConfirm 구조는 이 시나리오의 단계를 개별 함수로 나누어 읽기·이해하기 쉽게 해요. 주요 단계는 다음과 같아요.

  • AddPreSignUpTrigger — Lambda 핸들러를 PreSignUp 트리거의 호출 대상으로 추가합니다.
  • SignUpUser — 지정한 비밀번호로 알려진 사용자 테이블의 사용자를 가입시킵니다.
  • SignInUser — 사용자를 로그인시킵니다.
  • Run — 시나리오를 실행합니다.

PreSignUp 트리거를 Lambda 함수로 처리

핸들러는 PreSignUp 이벤트를 받아 Amazon DynamoDB 테이블에서 사용자를 조회하고, 사용자를 확인·검증할지 여부를 지정해요.

import (
	"context"
	"log"
	"os"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
	dynamodbtypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

const TABLE_NAME = "TABLE_NAME"

// UserInfo defines structured user data that can be marshalled to a DynamoDB format.
type UserInfo struct {
	UserName  string `dynamodbav:"UserName"`
	UserEmail string `dynamodbav:"UserEmail"`
}

// GetKey marshals the user email value to a DynamoDB key format.
func (user UserInfo) GetKey() map[string]dynamodbtypes.AttributeValue {
	userEmail, err := attributevalue.Marshal(user.UserEmail)
	if err != nil {
		panic(err)
	}
	return map[string]dynamodbtypes.AttributeValue{"UserEmail": userEmail}
}

type handler struct {
	dynamoClient *dynamodb.Client
}

// HandleRequest handles the PreSignUp event by looking up a user in an Amazon DynamoDB table and
// specifying whether they should be confirmed and verified.
func (h *handler) HandleRequest(ctx context.Context, event events.CognitoEventUserPoolsPreSignup) (events.CognitoEventUserPoolsPreSignup, error) {
	log.Printf("Received presignup from %v for user '%v'", event.TriggerSource, event.UserName)
	if event.TriggerSource != "PreSignUp_SignUp" {
		// Other trigger sources, such as PreSignUp_AdminInitiateAuth, ignore the response from this handler.
		return event, nil
	}
	tableName := os.Getenv(TABLE_NAME)
	user := UserInfo{
		UserEmail: event.Request.UserAttributes["email"],
	}
	log.Printf("Looking up email %v in table %v.\n", user.UserEmail, tableName)
	output, err := h.dynamoClient.GetItem(ctx, &dynamodb.GetItemInput{
		Key:       user.GetKey(),
		TableName: aws.String(tableName),
	})
	if err != nil {
		log.Printf("Error looking up email %v.\n", user.UserEmail)
		return event, err
	}
	if output.Item == nil {
		log.Printf("Email %v not found. Email verification is required.\n", user.UserEmail)
		return event, err
	}

	err = attributevalue.UnmarshalMap(output.Item, &user)
	if err != nil {
		log.Printf("Couldn't unmarshal DynamoDB item. Here's why: %v\n", err)
		return event, err
	}

	if user.UserName != event.UserName {
		log.Printf("UserEmail %v found, but stored UserName '%v' does not match supplied UserName '%v'. Verification is required.\n",
			user.UserEmail, user.UserName, event.UserName)
	} else {
		log.Printf("UserEmail %v found with matching UserName %v. User is confirmed.\n", user.UserEmail, user.UserName)
		event.Response.AutoConfirmUser = true
		event.Response.AutoVerifyEmail = true
	}

	return event, err
}

func main() {
	ctx := context.Background()
	sdkConfig, err := config.LoadDefaultConfig(ctx)
	if err != nil {
		log.Panicln(err)
	}
	h := handler{
		dynamoClient: dynamodb.NewFromConfig(sdkConfig),
	}
	lambda.Start(h.HandleRequest)
}

시나리오는 다음 헬퍼 구조로 구성돼요 (전체 소스는 GitHub에서 확인).

  • 공통 작업을 수행하는 구조 — IScenarioHelper 인터페이스와 ScenarioHelper 구조. 이 예제의 워크플로가 사용하는 공통 함수(스택 출력 가져오기, 사용자 테이블 채우기, 알려진 사용자 가져오기/추가하기, 최근 로그 이벤트 나열하기)를 정의해요.
  • Amazon Cognito 작업을 감싸는 구조 — CognitoActions 구조가 PreSignUp, UserMigration, PostAuthentication 트리거 업데이트, SignUp, SignIn, ForgotPassword, ConfirmForgotPassword, DeleteUser, AdminCreateUser, AdminSetUserPassword 작업을 캡슐화해요.
  • DynamoDB 작업을 감싸는 구조 — DynamoActions 구조가 테이블 채우기(PopulateTable), 검색(Scan), 사용자 추가(AddUser) 작업을 캡슐화해요.
  • CloudWatch Logs 작업을 감싸는 구조 — CloudWatchLogsActions 구조가 Lambda 함수의 최신 로그 스트림과 로그 이벤트를 가져오는 작업을 캡슐화해요.
  • CloudFormation 작업을 감싸는 구조 — CloudFormationActions 구조가 CloudFormation 스택 출력을 구조화된 형식으로 가져오는 작업을 캡슐화해요.
  • 리소스 정리 — Resources 구조가 예제 중 생성된 AWS 리소스를 추적하고, 끝나면 정리해요.

API 세부 정보는 AWS SDK for Go API Reference의 다음 항목을 참고하세요. DeleteUser, InitiateAuth, SignUp, UpdateUserPool.

JavaScript — SDK for JavaScript (v3)

참고

전체 예제와 설정·실행 방법은 AWS Code Examples Repository(GitHub)에서 볼 수 있어요. JavaScript (v3) 예제는 복잡한 예제를 간소화하기 위해 공유 Scenario 러너를 사용해요.

대화형 "시나리오" 실행 구성

시나리오 러너가 예제 단계를 조율해요. AutoConfirm 시나리오는 알려진 사용자를 자동 확인하는 것을 시연해요. 흐름은 사용자 풀의 PreSignUp 트리거에 AutoConfirmHandler를 연결하고, 가입할 사용자를 선택하고, 로그인한 뒤 사용자를 삭제하고, 리소스 정리를 안내해요.

PreSignUp 트리거용 Lambda 함수 핸들러

TypeScript로 작성된 핸들러는 PreSignUp 트리거 이벤트를 받아 DynamoDB 사용자 저장소에서 이메일을 조회하고, 저장된 UserName이 이벤트의 UserName과 일치하면 autoConfirmUser와 autoVerifyEmail을 true로 설정해요.

import type { PreSignUpTriggerEvent, Handler } from "aws-lambda";
import type { UserRepository } from "./user-repository";
import { DynamoDBUserRepository } from "./user-repository";

export class PreSignUpHandler {
  private userRepository: UserRepository;

  constructor(userRepository: UserRepository) {
    this.userRepository = userRepository;
  }

  private isPreSignUpTriggerSource(event: PreSignUpTriggerEvent): boolean {
    return event.triggerSource === "PreSignUp_SignUp";
  }

  private getEventUserEmail(event: PreSignUpTriggerEvent): string {
    return event.request.userAttributes.email;
  }

  async handlePreSignUpTriggerEvent(
    event: PreSignUpTriggerEvent,
  ): Promise<PreSignUpTriggerEvent> {
    console.log(
      `Received presignup from ${event.triggerSource} for user '${event.userName}'`,
    );

    if (!this.isPreSignUpTriggerSource(event)) {
      return event;
    }

    const eventEmail = this.getEventUserEmail(event);
    console.log(`Looking up email ${eventEmail}.`);
    const storedUserInfo =
      await this.userRepository.getUserInfoByEmail(eventEmail);

    if (!storedUserInfo) {
      console.log(
        `Email ${eventEmail} not found. Email verification is required.`,
      );
      return event;
    }

    if (storedUserInfo.UserName !== event.userName) {
      console.log(
        `UserEmail ${eventEmail} found, but stored UserName '${storedUserInfo.UserName}' does not match supplied UserName '${event.userName}'. Verification is required.`,
      );
    } else {
      console.log(
        `UserEmail ${eventEmail} found with matching UserName ${storedUserInfo.UserName}. User is confirmed.`,
      );
      event.response.autoConfirmUser = true;
      event.response.autoVerifyEmail = true;
    }
    return event;
  }
}

const createPreSignUpHandler = (): PreSignUpHandler => {
  const tableName = process.env.TABLE_NAME;
  if (!tableName) {
    throw new Error("TABLE_NAME environment variable is not set");
  }

  const userRepository = new DynamoDBUserRepository(tableName);
  return new PreSignUpHandler(userRepository);
};

export const handler: Handler = async (event: PreSignUpTriggerEvent) => {
  const preSignUpHandler = createPreSignUpHandler();
  return preSignUpHandler.handlePreSignUpTriggerEvent(event);
};

시나리오는 다음 모듈로 구성돼요 (전체 소스는 GitHub에서 확인).

  • CloudWatch Logs 작업 모듈 — Lambda 함수의 최신 로그 스트림과 로그 이벤트를 가져오는 함수.
  • Amazon Cognito 작업 모듈 — PreSignUp 핸들러 연결(addPreSignUpHandler), 사용자 가입(signUpUser), 로그인(signIn), 사용자 조회(getUser), 사용자 삭제(deleteUser).
  • DynamoDB 작업 모듈 — 제공된 항목으로 테이블 채우기(populateTable).
  • 다른 시나리오와 공유되는 단계 — skipWhenErrors, getStackOutputs, promptForStackName, promptForStackRegion, logCleanUpReminder.

API 세부 정보는 AWS SDK for JavaScript API Reference의 다음 항목을 참고하세요. DeleteUser, InitiateAuth, SignUp, UpdateUserPool.

AWS SDK 개발자 안내서와 코드 예제의 전체 목록은 AWS SDK로 Lambda 사용을 참고하세요. 이 항목에는 시작하기 정보와 이전 SDK 버전에 대한 세부 정보도 포함돼 있어요.

더 알아보기 (Learn more)