API GatewayのオーソライザーにCognitoを使用してみた

# 背景・目的
[Amazon Cognito ユーザープールをオーソライザーとして使用して REST API へのアクセスを制御する](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html)のチュートリアルを試してみます。

# まとめ
今回は、下記の構成で試しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/3ca4151b-b28a-16c1-d7a9-52456d458590.png)



# 概要
[Amazon Cognito ユーザープールをオーソライザーとして使用して REST API へのアクセスを制御する](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html)を基に、整理します。

> IAM ロールとポリシーまたは Lambda オーソライザー (以前のカスタムオーソライザー) の代わりに、Amazon Cognito ユーザープールを使用して、Amazon API Gateway の API にアクセスできるユーザーを制御します。

- Amazon Cognitoユーザープールを使用して、API GatewayのAPIにアクセスできるユーザを制御する

> API で Amazon Cognito ユーザープールを使用するには、COGNITO_USER_POOLS タイプのオーソライザーを作成してから、そのオーソライザーを使用する API メソッドを構成する必要があります。API がデプロイされた後、クライアントはまずユーザーをユーザープールに署名し、ユーザーの ID またはアクセストークンを取得してから、トークンの 1 つ (通常はリクエストの Authorization ヘッダーに設定されている) で API メソッドを呼び出す必要があります。API 呼び出しは、必要なトークンが提供され、提供されたトークンが有効な場合にのみ成功します。そうでない場合、クライアントは認証された認証情報を持たないため呼び出しを許可されません。

- Cognitoユーザプールを使用するには、COGNITO_USER_POOLタイプのオーソライザを作成
- そのオーソライザーを使用するAPIメソッドを構成する
- クライアントは、ユーザをユーザプールに署名し、ユーザID、アクセストークンを取得しトークンの1つでAPIメソッドを呼び出す
- API呼び出しは、必要なトークンが提供される
    - 提供されたトークンが有効な場合にのみ成功する
    - そうではない場合、クライアントは認証された認証情報を持たないため呼び出しは許可されない


> ID トークンは、サインインされたユーザーの ID リクエストに基づいて API 呼び出しを承認するために使用されます。アクセストークンは、指定されたアクセス保護されたリソースのカスタムスコープに基づいて API 呼び出しを承認するために使用されます。

- IDトークンは、サインインされたユーザのIDリクエストに基づき、API呼び出しを承認するために使用される
- アクセストークンは、指定されたアクセス保護されたリソースのカスタムスコープに基づき、API呼び出しを承認するために使用する

## ユーザープールでのトークンの使用
[ユーザープールでのトークンの使用](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html)を基に整理します。

> トークンを使用してユーザーを認証し、リソースへのアクセス権を付与します。トークンのクレームはユーザーに関する情報です。ID トークンには、ユーザー名、苗字、E メールアドレスなど、身元に関するクレームが含まれています。アクセストークンには、認証されたユーザーがサードパーティー API、Amazon Cognito ユーザーのセルフサービス API オペレーション、および UserInfo エンドポイント にアクセスできる scope のようなクレームが含まれています。アクセストークンと ID トークンの両方に、ユーザープール内でのユーザーのグループメンバーシップを示す cognito:groups クレームが含まれています。ユーザープールグループの詳細については、「ユーザープールにグループを追加する」を参照してください。

- トークンを使用してユーザを認証し、リソースへのアクセス権を付与する
- トークンのクレームは、ユーザに関する情報
- IDトークンには、ユーザ名等の身元に関するクレームが含まれる
- アクセストークンには、
    - 認証されたユーザがサードパーティAPI
    - UserInfoエンドポイントにアクセスできるスコープ
- アクセストークンとIDトークンの両方には、ユーザプール内でのユーザグループメンバーシップを示すcongnito:groupsクレームが含まれる

> Amazon Cognito には、新しいトークンの取得や既存のトークンの取り消しに使用できる更新トークンもあります。トークンを更新して、新しい ID とアクセストークンを取得します。トークンを取り消して、更新トークンによって許可されているユーザーアクセスを取り消します。

- Cognitoには、新しいトークンの取得や既存のトークンの取り消しに使用できる更新トークンもある
    - トークンを更新して、新しいIDとアクセストークンを取得する
    - トークンを取り消して、更新トークンにより許可されているユーザアクセスを取り消す

> Amazon Cognito は、Base 64 でエンコードされた文字列としてトークンを発行します 任意の Amazon Cognito ID またはアクセストークンを base64 からプレーンテキスト JSON にデコードできます。Amazon Cognito の更新トークンは暗号化されており、ユーザープールのユーザーと管理者に対して透過的ではなく、ユーザープールのみが読み取ることができます。

- Cognitoは、Base64でエンコードされた文字列としてトークンを発行する
- 任意のCognito IDまたはアクセストークンをbase64からプレーンテキストJSONにデコードできる
- Cognitoの更新トークンは暗号化されており、ユーザープールのユーザと管理者に対して透過的ではなく、ユーザプールのみが読み取れる

> トークンを使用した認証
ユーザーがアプリケーションにサインインすると、Amazon Cognito がログイン情報を検証します。ログインが正常に行われると、Amazon Cognito がセッションを作成し、認証されたユーザーの ID トークン、アクセストークン、および更新トークンを返します。これらのトークンを使用して、ダウンストリームのリソースや Amazon API Gateway などの API へのアクセス権をユーザーに付与できます。または、これらのトークンを他の AWS のサービスにアクセスするための一時的な AWS 認証情報と交換することができます。

- ユーザがアプリケーションにサインインすると、Cognitoがログイン情報を検証する
- ログインが正常に行われると、Cognitoがセッションを作成し、認証されたユーザのIDトークン、アクセストークン、更新トークンを返す
    - トークンを使用し、ダウンストリームのリソース、API GatewayなどのAPIへのアクセス権をユーザに付与できる
    - これらのトークンを他のAWSサービスにアクセスするための一時的なAWS認証情報と交換できる

> トークンの保存
アプリケーションでは、さまざまなサイズのトークンを保存する必要があります。トークンのサイズは、クレームの追加、エンコーディングアルゴリズムの変更、暗号化アルゴリズムの変更による理由（ただし、これらに限定されない）で変更される場合があります。ユーザープールでトークンの取り消しを有効にした場合も、Amazon Cognito が JSON ウェブトークンにクレームを追加するので、そのトークンのサイズは増加します。アクセストークンと ID トークンに対し、新たなクレーム origin_jti および jti が追加されます。トークンの取り消しの詳細については、「トークンの取り消し」を参照してください。

- アプリケーションでは、様々なサイズのトークンを保存する必要がある

> トークンをカスタマイズする
Amazon Cognito からアプリに渡すアクセストークンと ID トークンをカスタマイズできます。トークン生成前の Lambda トリガーでは、トークンクレームを追加、変更、および抑制できます。トークン生成前トリガーは、Amazon Cognito からデフォルトのクレームセットを送信する先の Lambda 関数です。クレームには、OAuth 2.0 スコープ、ユーザープールグループのメンバーシップ、ユーザー属性などが含まれます。この関数は、必要に応じてランタイムに変更を加え、更新したトークンクレームを Amazon Cognito に返すことができます。

> バージョン 2 のイベントによるアクセストークンのカスタマイズには追加料金がかかります。詳細については、「Amazon Cognito の料金」を参照してください。

- Cognitoからアプリに渡すアクセストークンとIDトークンをカスタマイズできる
- トークン生成前のLambdaトリガーでは、トークンクレームを追加、変更、抑制できる
- トリガー生成前トリガーは、Cognitoからのデフォルトのクレームセットを送信する先のLambda関数
- クレームには、OAuth2.0スコープ、ユーザグループのメンバーシップ。ユーザ属性などが含まれる
- 必要に応じてランタイムに変更を加えて、更新したトークンクレームをCognitoに返す事ができる

# 実践
クラスメソッド様の[Cognitoで認証されたユーザーだけがAPI Gatewayを呼び出せるオーソライザーを使ってみた](https://dev.classmethod.jp/articles/api-gateway-cognito-authorizer/)を基に試してみます。（ありがとうございます！）

## 1. ユーザプールの作成
REST API 用の Amazon Cognito ユーザープールを作成する
1. AWSにサインインします
1. Cognitoに移動し、「ユーザプールを作成」をクリックします
1. プロバイダーのタイプはそのまま（Cognitoユーザプールのみ）
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/a18eb12c-1d4c-a325-df52-e69425db6a53.png)

1. Cognitoユーザプールのサインインオプションに「ユーザ名」を選択し、「次へ」をクリックします
1. セキュリティ要件のパスワードポリシーでは、「Cognitoのデフォルト」を選択します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/4929e549-9c78-20d9-36e4-788643e1f1fb.png)
1. 多要素認証では、MFAなしを選択します
1. ユーザアカウントの復旧では、下記を指定し、「次へ」をクリックします
    - セルフサービスアカウントの復旧を有効化
    - ユーザアカウント復旧メッセージの配信方法では、Eメールのみ
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/1cdeb9ab-8904-0b19-1efa-1f2b68405f93.png)

1. サインアップエクスペリエンスを設定のセルフサービスのサインアップでは、**自己登録を無効**にします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/9e4c4fed-bb76-f627-95b5-c3eba7268fb6.png)

1. 属性検証とユーザアカウントの確認では、下記を選択します
    - Cognitoアシスト型の検証および確認
        - 自動的に送信：Cognitoが検証と確認のために、自動的に送信して検証および確認
        - 検証する属性：Eメールメッセージを送信、Eメールアドレスを検証
    - 属性変更の確認
        - 未完了の更新があるときに元の属性をアクティブに保つ
        - 未完了の更新があるときのアクティブな属性値：Eメールアドレス
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/cebde915-15e6-6ac0-c6ce-1e0446a081c4.png)
1. 必須の属性では、emailが選択されていることを確認し、「次へ」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/e42b1440-bd1f-aad0-9e81-21c0292d46b3.png)
1. メッセージ配信を設定では、Eメールプロバイダーを「CognitoでEメールを送信」を選択し「次へ」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/fd719713-9221-5b3c-e21f-07090775b221.png)
1. アプリケーションを統合では、任意のユーザプール名を指定します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/bc8dd801-687d-0ec0-2ce6-3e96c93ebae4.png)
1. ホストされた認証ページでは、「CognitoのホストされたUIを使用」を**チェックしません*
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/e347333c-1c86-35b7-2609-cb97ef6bec29.png)

1. 最初のアプリケーションクライアントでは、下記を指定します
    - ①アプリケーションタイプ：パブリッククライアント
    - ②アプリケーションクライアント名：任意
    - ③クライアントシークレット：クライアントのシークレットを生成しない
    - ④高度なアプリケーションクライアントの設定＞ALLOW_USER_SRP
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/43cc95e2-17cb-0383-48f7-1ac45b75b5b6.png)

1. その他は、デフォルトのまま「次へ」をクリックします
1. 確認と作成画面で「ユーザプールを作成」をクリックします

## 2. ユーザを作成する
1. 作成したユーザプールを選択します
1. 「ユーザ」タブで「ユーザを作成」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/5db52159-b749-898e-7818-140cf954779d.png)

1. 下記を入力し、「ユーザを作成」をクリックします
    - ユーザ名
    - Eメールアドレス
    - 仮パスワード：パスワードの生成
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/00132dcf-c026-a641-eaf2-5e17922cb4a8.png)

1. 確認ステータスは、パスワードを強制的に変更の状態です
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/85642fa8-6b21-5c4f-d2a2-c4f9630b2f9b.png)

### サインイン
1. 下記のコマンドで仮パスワードで、認証フローを開始します
    ```
    aws cognito-idp initiate-auth \
        --auth-flow USER_PASSWORD_AUTH \
        --client-id <your_app_client_id> \
        --auth-parameters USERNAME=<your_username>,PASSWORD=<temporary_password>
    ```
1. レスポンスが返されるので、Sessionをコピーします
    ```
    $ aws cognito-idp initiate-auth \
        --auth-flow USER_PASSWORD_AUTH \
        --client-id <your_app_client_id> \
        --auth-parameters USERNAME=<your_username>,PASSWORD=<temporary_password>
    {
        "ChallengeName": "NEW_PASSWORD_REQUIRED",
        "Session": "XXXXXX",
        "ChallengeParameters": {
            "USER_ID_FOR_SRP": "XXXXXX",
            "requiredAttributes": "[]",
            "userAttributes": "{\"email\":\"XXXXX\"}"
        }
    }
    $ 
    ```

1. 上記で取得したSessionを貼り付けて、チャレンジを送ります
    ```
    aws cognito-idp respond-to-auth-challenge \
        --client-id <your_app_client_id> \
        --challenge-name NEW_PASSWORD_REQUIRED \
        --session <session> \
        --challenge-responses USERNAME=<your_username>,NEW_PASSWORD=<new_password>
    
    ```
1. 下記のレスポンスが返されます
    ```
    $ aws cognito-idp respond-to-auth-challenge \
        --client-id <your_app_client_id> \
        --challenge-name NEW_PASSWORD_REQUIRED \
        --session <session> \
        --challenge-responses USERNAME=<your_username>,NEW_PASSWORD=<new_password>
    {
        "ChallengeName": "MFA_SETUP",
        "Session": "XXXXXXX",
        "ChallengeParameters": {
            "MFAS_CAN_SETUP": "[\"SOFTWARE_TOKEN_MFA\"]"
        }
    }
    ```
1. ステータスは、「有効」に変わりました。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/aecc4f6a-2260-a907-26f3-2cdd4cf91d4f.png)

1. ユーザを選択します
1. アクションで「MFA設定の更新」をクリックします

## 3. API Gatewayの作成
1. API Gatewayに移動します
1. REST APIでインポートを選択します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/1efd8704-ea20-7713-7a9c-9ca42052f36e.png)


1. 下記を入力し、「APIを作成」をクリックします
    - サンプルAPI
    - APIエンドポイントタイプ：リージョン
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/37cd0b19-76df-efdb-4c2a-142f7b12abad.png)

1. APIのデプロイをクリックします
1. 「新しいステージ」を選択し、「dev」とします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/a211bfac-746f-3330-0a05-54b7317c073e.png)

1. エンドポイントが割り当てられました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/1c520a33-4d27-3aa0-4b10-ea9b1b9d1fc8.png)

### アクセス確認
1. 上記のdevのエンドポイントに対してリクエストします。表示されました。この段階では、認証されなくてもアクセス可能です
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/9a9faf16-9efb-8cd8-286a-10840e08359b.png)

## 4. オーソライザの作成と設定
1. 上記で作成したAPIを選択します
1. ナビゲーションペインで「オーソライザー」をクリックします
1. 「オーソライザーを作成」をクリックします
1. 下記を指定し、「オーソライザーを作成」をクリックします
    - オーソライザー名：任意
    - オーソライザーのタイプ：Cognito
    - Cognitoユーザプール：上記で作成したCognitoユーザプールを指定
    - トークンのソース：Authorization
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/3bd9cbca-a314-385e-30bf-7cf99aa2a1e1.png)

### 動作確認

1. Authorizerができていました。「オーソライザーをテスト」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/6b61f81d-3b06-496b-ae83-e48a19a8d5ff.png)
1. 401エラーになりました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/522a2648-45f5-f0d6-0cda-8d58cf2947e2.png)

1. CLIでIDトークンを取得してみます
    ```
    $ aws cognito-idp initiate-auth \
        --auth-flow USER_PASSWORD_AUTH \
        --client-id <your_app_client_id> \
        --auth-parameters USERNAME=<your_username>,PASSWORD=<temporary_password>
    {
        "ChallengeParameters": {},
        "AuthenticationResult": {
            "AccessToken": "XXXX",
            "ExpiresIn": 3600,
            "TokenType": "Bearer",
            "RefreshToken": "XXXXX",
            "IdToken": "XXXX"
        }
    }    
    ```
1. 取得したIDトークンを貼り付け、「オーソライザーをテスト」をクリックします。今度は成功しました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/64783384-409d-9595-0862-3e22ddf69edc.png)

### オーソライザーをAPI Gatewayのメソッドに設定する
1. API GatewayリソースのGETをクリックします
1. メソッドリクエストで編集をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/e6aad52e-20bc-1464-df3b-5e6ce301677b.png)
1. メソッドリクエストの設定の認可に、作成したオーソライザーを指定し、「保存」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/07b441ab-4956-2452-d4ca-8315906e8b00.png)
1. 「APIをデプロイ」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/5c64e053-71c5-84ef-7d2e-eda6c3b723cc.png)

### エンドポイントにアクセスし動作確認する
:::note info
ヘッダーにIDトークンを指定するため、CLIで確認しています。
:::

1. 先ほどと同様のエンドポイントにアクセスすると、エラーになりました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/dfb0e8f5-0fe8-496a-8cd2-1d67766a10cd.png)

1. CLIでも同様です
    ```
    $ curl -X GET https://XXXXXX.execute-api.us-east-2.amazonaws.com/dev
    {"message":"Unauthorized"}
    $ 
    ```
1. IDトークンを指定し、リクエストするとアクセスできました
    ```
    $  curl -X GET https://XXXXXX.execute-api.us-east-2.amazonaws.com/dev \
    > -H "Authorization: Bearer XXXXXXXX"
    <html>
        <head>
            <style>
            body {
                color: #333;
                font-family: Sans-serif;
                max-width: 800px;
                margin: auto;
            }
            </style>
        </head>
        <body>
            <h1>Welcome to your Pet Store API</h1>
            <p>
                You have successfully deployed your first API. You are seeing this HTML page because the <code>GET</code> method to the root resource of your API returns this content as a Mock integration.
            </p>
            <p>
                The Pet Store API contains the <code>/pets</code> and <code>/pets/{petId}</code> resources. By making a <a href="/dev/pets/" target="_blank"><code>GET</code> request</a> to <code>/pets</code> you can retrieve a list of Pets in your API. If you are looking for a specific pet, for example the pet with ID 1, you can make a <a href="/dev/pets/1" target="_blank"><code>GET</code> request</a> to <code>/pets/1</code>.
            </p>
            <p>
                You can use a REST client such as <a href="https://www.getpostman.com/" target="_blank">Postman</a> to test the <code>POST</code> methods in your API to create a new pet. Use the sample body below to send the <code>POST</code> request:
            </p>
            <pre>
    {
        "type" : "cat",
        "price" : 123.11
    }
            </pre>
        </body>
    </html>
    $ 
    ```

# 考察
今回は、API GatewayのオーソライザにCognitoのオーソライザを指定してみました。
API Gatewayはデプロイするとグローバルに公開されるので、利用者を限定したいときに簡単に制限できるのは嬉しいですね。

# 参考
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html

https://aws.amazon.com/jp/blogs/news/amazon-api-gateway-cognito-waf/

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html

https://qiita.com/zumax/items/6937b3ecb501b6ca50bb

https://qiita.com/zumax/items/b11bb6653f3f46adb674

https://dev.classmethod.jp/articles/api-gateway-cognito-authorizer/