【AWS】Lambdaにフロント側からデータを渡す

# 背景
AWS LambdaでAmazon Cognitoのメールアドレスや電話番号を変更時にフロント側から送られてくるデータを使用して重複チェックをしたい時があって、少しつまづいたのでどうにかできないかということがありました。

変更のタイミングでAmazon Cognito側のLambdaトリガーを使って実装していたのですが、トリガー発火した時のeventの中身には登録されているAmazon Cognitoのユーザープールのデータが取れるので、フロント側から変更したデータは得られていないことが発覚。

API Gatewayをトリガーにしたらデータも取得できるので可能ですが、今回はAmazon Cognito + AWS Lambdaでの実装が必要だということだったので、別の方法が必要でした。

# 解決方法
今回はフロント側からAmplifyのAuthを使用した方法で変更のデータが送られるので、そのときに**ClientMetadata**を送信することで対応しました。

ただ、状況によってはClientMetadataの使用が非推奨のこともあるので、実装する際は要件などに気をつける必要がありそうです。

# 実行環境
Python 3.9

# 実装コード(例)
フロント側(React)

今回はAmazon Cognitoのメールアドレスの属性を変更するので、変更のメールアドレスのデータを`Auth.updateUserAttributes`の引数に渡します。

ここで`ClientMetadata`に該当する第3引数に`data`を渡すことでバックエンド(Lambda)側でデータを使うことができます。

```react
import { Auth } from '@aws-amplify';
import { Authenticator } from '@aws-amplify/ui-react';

function App() {
    const changeAttribute = async () => {
        const user = await Auth.currentAuthenticatedUser(); // ログインしているユーザーデータ
        const data = {'email': 'xxxxxx@example.com'}; // 変更するデータ(今回はメールアドレス)

        return await Auth.updateUserAttributes(
            user,
            data,
            data // ClientMetadataの引数
        );

    return (
        <Authenticator>
            {({ signOut, user }) => (
                <div>
                    <button onClick={signOut}>Sign Out</button>
                    <button onClick={changeAttribute}>Change Attribute</button>
                </div>
            )}
        </Authenticator>
    );
}
```

バックエンド側(Python)

バックエンド(Lambda)側では、Amazon CognitoのLambdaトリガーから渡される`event`内にフロント側から送信された`ClientMetadata`を`event['request']['clientMetadata']`で取得できます。

```python
import boto3

def lambda_handler(event, context):
    if event['triggerSource'] == 'CustomMessage_UpdateUserAttribute':
        try:
            if event['request']['clientMetadata'].get('email') is not None:
                duplicated_user = cognito_client.list_users(
                    UserPoolId = event['userPoolId'],
                    Filter = f"email =\"{ event['request']['clientMetadata']['email'] }\""
                )
                
                if len(duplicated_user['Users']) > 0:
                    raise Exception('このメールアドレスはすでに登録されています。べつのメールアドレスを登録してください。')
            
            return event
        
        except Exception as e:
            print(e)
        
```

これで、変更したいメールアドレスのユーザーがすでに存在していた場合は、エラーを発生させます。

# 注意点
AWS SDK for Python(boto3)公式サイトに**ClientMetadataの使用上の注意**が書かれています。
表記は英語なので簡単に翻訳して引用します。
> ClientMetadataパラメーターを使う場合、Amazon Cognitoは以下のことをしないということに注意してください:
>
> ①ClientMetadataの値を保存しません。
> また、このデータはCognitoのユーザープールに設定されたLambdaトリガーでのみ使用可能です。
> 
> ②ClientMetadataの値はバリデーションされません。
>
> ③ClientMetadataの値は暗号化されません。
> そのため、暗号化が必要なデータなどの送信はしないようにしてください。

このような注意点がありますので、上述のように要件などに合わせて使う必要があります。

# まとめ
AWSを触り始めてまだ1か月ほどで、ここまで来るのに苦労しました...

解決したかと思いきや、注意点もあったのでなんともいいがたいところです...

もしほかの方法やご指摘がありましたら、コメントにお願いいたします！

# 引用記事等
[https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp.html#CognitoIdentityProvider.Client.get_user](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp.html#CognitoIdentityProvider.Client.get_user)