CognitoでAPI Gatewayのオーサライザーを作成しよう

# 🔷 はじめに

API Gatewayに作成したAPIを、認証済みのユーザーのみに利用を許可したい場合、API Gatewayのオーサライザーという機能を使う事で実現できます。

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/335f7d95-60cf-9e98-bbf3-21627b09e308.png)

フロントエンドからAPIにリクエストを送る際に、認証情報を付加してリクエストを送ると、オーサライザーで認証情報が正しいものか判断して、正しければAPIの利用が許可されて、APIからフロントエンドにレスポンスが返るという流れになります。

### 🔶 オーサライザー
API Gatewayにある、認可を行う機能です。
認可は、リクエスト時に付加されている認証情報を解析して、正しいものであればAPIの利用を許可する、という形で行います。
それだけの機能なので、Lambdaで自作したり、[Auth0](https://auth0.com/docs/integrations/aws-api-gateway/custom-authorizers)ののような外部サービスを利用したり、実現手段はいくつかありますが、今回はCognitoを使用して認可機能を実現します。

### 🔶 Cognito

https://aws.amazon.com/jp/cognito/

>Amazon Cognito は、ウェブアプリケーションやモバイルアプリケーションの認証、許可、ユーザー管理をサポートしています。ユーザーは、ユーザー名とパスワードを使用して直接サインインするか、Facebook、Amazon、Google などのサードパーティーを通じてサインインできます。

今回は、Cognitoが持つ認可機能を利用して、オーサライザーを作成します。

![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/3dad528a-02f3-db30-28f8-06493fce1f6a.png)


### 🔶 認証情報
フロントエンドがリクエスト時に付加する認証情報を、どのように取得するかと言うと、Cognitoに発行してもらいます。
認証情報というのは、簡単に言うとIDトークンと呼ばれる、ユーザー情報等をエンコードした文字列です。  

Cognitoにユーザーの認証情報を発行してもらう方法として、今回は[ユーザープール認証フロー](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html)で行います。

![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/a332a233-9f46-1f00-138b-feeed4bc67c8.png)

流れとしては上図になりますが、もう少し細かい流れを言うと、事前にCognitoのユーザープール（後述）にユーザーを登録した上で、以下のようになります。

1. フロントエンドがCognitoの**InitiateAuth API**に、ユーザーのIDとPWを渡す。
1. CognitoがIDとPWでユーザー認証を行い、問題が無ければChallengeというパラメータをフロントエンドに返す。
1. フロントエンドがCognitoの**RespondToAuthChallenge API**に、Challengeというパラメータを渡す。
1. CognitoがChallengeの確認を行い、認証した証明として認証情報（IDトークン）をフロントエンドに返す。

ただし、実際に実装する際はSDK等が用意されているので、コードとしてシンプルなものになります。

一見すると、OpenID Connectのフローのようですが、**Cognitoは全く別物の、AWS独自の認証フロー**になります。
# 🔷 作業手順

作業手順は以下になります。

1. API GatewayでAPIの作成
1. Cognitoでユーザープールの作成
1. ユーザープールに、ユーザーを作成
1. IDトークンを取得する、フロントエンドの作成
1. API Gatewayでオーサライザーの作成

# 🔷 API GatewayでAPIの作成

今回はテストなので、標準で用意されている、PetStoreというサンプルAPIを使用します。
<img width="1032" alt="101.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/19623e8f-410c-3774-c2bb-584830c20258.png">
上図の「APIの例」を選択すると、PetStoreというAPIが作られます。
<img width="1095" alt="102.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/8dab276a-d17b-2aec-0f3b-9753222048e1.png">

# 🔷 Cognitoでユーザープールの作成

今回は[ユーザープール認証フロー](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html)で、Cognitoにユーザーの認証情報のリクエストを行います。

そのためには、Cognitoにユーザーを登録する必要があります。
そして、Cognitoにユーザーを登録するには、ユーザープールというディレクトリを用意する必要があります。

**1. ユーザープールを新規作成する。**

まずはCognitoの画面を開き、[ユーザープールの管理]ボタンをクリックします。
<img width="1068" alt="201.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/227ee580-036b-45cc-0e85-bd1c155af210.png">
[ユーザープールを作成する]ボタンをクリックします。 
<img width="1070" alt="202.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/aec0b441-32c6-840b-a9f5-91fac40c4507.png">
プール名を入力して、[ステップに従って設定する]ボタンをクリックします。 
<img width="1096" alt="203.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/76504c35-a70f-17e1-df63-4320cc742b67.png">

⬛プール名
任意の名称をセットします。

**2. 作成するユーザープールの、属性の設定。** 

今回は、ユーザーがサインインする際は、メールアドレスでサインインするようにします。
[検証済みの E メールアドレスでのサインインも許可]にチェックを入れます。
<img width="1240" alt="204_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/7e05fba1-f0b0-d55f-59df-f9769a311701.png">
下までスクロールすると、標準属性のemailにチェックが入っているので、今回はテストなので条件を緩くするため、チェックを外します。

[次のステップ]ボタンをクリックします。
<img width="1239" alt="204_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/d18556e3-d7bb-85d9-3f0a-dd8b016f61a4.png">

**3. 作成するユーザープールの、ポリシーの設定。** 

パスワード強度のチェックがすべて入っていますが、今回はテストなので、チェックを全て外して条件を緩くします。

[次のステップ]ボタンをクリックします。 
<img width="1236" alt="205.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/2aac8840-b55a-4a6e-db1b-145928eed200.png">

**4. 作成するユーザープールの、アプリクライアントの設定。** 

左メニューの[アプリクライアント]をクリックします。
[アプリクライアントの追加]をクリックします。
<img width="1095" alt="206.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/e754cb39-b2f8-d2ab-c0a4-cf9c7739cf13.png">
以下の必要項目をセットします。
<img width="1236" alt="207.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/e5ad0a94-0e99-6597-0180-6fa69d6c1205.png">
⬛アプリクライアント名
任意の名称をセットします。

⬛[クライアントシークレットを生成]チェックボックス
今回は手順簡略化のため外します。

⬛[サーバーベースの認証でサインイン API を有効にする (ADMIN_NO_SRP_AUTH)] チェックボックス
チェックを入れて、サーバー側認証を有効にします。

[プールの詳細に戻る]をクリックします。
<img width="1238" alt="208.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/19f618b3-88a4-4837-36d3-96fb6745a373.png">

**5. ユーザープールの作成実行。** 

最後に確認画面が表示されるので、[プールの作成]ボタンをクリックします。
<img width="1238" alt="209.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/ce19577d-b0d8-ce26-8fab-8eb890e554be.png">

これでユーザープールが作成されました。
<img width="1235" alt="210.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/ab9bd20f-f3c7-5a92-bb5a-19ccd85c657f.png">

# 🔷 ユーザープールに、ユーザーを作成

左メニューの[ユーザーとグループ]をクリックします。
[ユーザーの作成]ボタンをクリックします。
<img width="1240" alt="301.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/41b58ccb-69b4-907e-d350-f2dbdff57ad3.png">
ユーザーの作成ダイアログが表示されるので、以下の必要項目をセットします。
<img width="616" alt="302.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/377fcceb-60d2-19b3-df6b-f6ea0f112a1e.png">
⬛ユーザー名
任意の名称をセットします。

⬛[Eメール]チェックボックス
ユーザー登録に成功すると、IDとPWが記載された招待メールが届くので、チェックを入れます。

⬛仮パスワード
任意の仮パスワードをセットします。
今回はユーザープール作成時にポリシーを緩くしたので、8文字以上であれば問題ないです。
ただし、仮パスワードは、ユーザー側で変更が必須となります。

⬛[電話番号を検証済みにしますか?]チェックボックス
今回は電話番号を登録しないので、チェックを外します。

⬛Eメール
任意のメールアドレスをセットします。
適当に入力しても登録はできるけど、招待メールの送信先になるので、テ
ストにしても[使い捨てメール](https://10minutemail.net/)でも良いので有効なメールアドレスをセットしましょう。

これでユーザーが登録できました。
<img width="1235" alt="303.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/7f9d50e7-2a2e-67e0-9393-fb133a19251c.png">

アカウントのステータスを確認すると、**FORCE_CHANGE_PASSWORD**というパスワード変更が必要な状態になっています。
管理者が作成したユーザーは、パスワードの変更が必須ですが、後述のフロントエンド処理の中で、パスワード変更も行いたいと思います。

参考：[例: SDK for JavaScript で AdminCreateUser API を使用して作成されたユーザーの新しいパスワードの認証と設定](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/using-amazon-cognito-identity-user-pools-javascript-example-authenticating-admin-created-user.html)



# 🔷 IDトークンを取得する、フロントエンドの作成


今回一番面倒な、IDトークンを取得する、フロントエンドを作成します。

一番面倒とは言うものの、サンプルソースやSDKは用意されているので、IDとPWを入力して実行する画面を用意して、裏側の処理はサンプルソースをコピーするだけで完成します。

**✅ちなみに、完成したものは、以下のリポジトリに置いてあります。**
https://github.com/tamura4278/sampleFrontend4Cognito

こちらをダウンロードして実行すれば、本章は読み飛ばしても問題ありません。

### 🔶 Cognito SDKの取得
フロントエンドの実装方法として、SDKを使う方法と、[AWS Amplifyライブラリ](https://aws-amplify.github.io/docs/js/authentication)を使う方法があるが、今回はJavascriptのSDKを使用します。

参考：[Amazon Cognito ユーザープール に JavaScript アプリを追加する](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/setting-up-the-javascript-sdk.html)

JavascriptのSDKですが、直接ダウンロードする方法はありません。
ドキュメントによると、Node.jsの環境がインストールされている前提で、以下の手順で取得できます。

1.  任意のフォルダで、`npm init`を実行する。
1. `npm install amazon-cognito-identity-js`を実行する。
1. 「node_modules」フォルダが作られ、/node_modules/amazon-cognito-identity-js/dist/amazon-cognito-identity.min.jsにSDKのライブラリが出力される。

参考：[Install using separate JavaScript file](https://github.com/aws-amplify/amplify-js/tree/master/packages/amazon-cognito-identity-js#install-using-separate-javascript-file)
### 🔶 フロントエンドの作成
SDKを利用した認証処理のサンプルソースは、以下のドキュメントに記載されています。

参考：[例: JavaScript SDK の使用](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-javascript-examples.html)

ただし、今回はユーザーのパスワード変更も併せて行いますので、以下のドキュメントのサンプルソースも追記します。

参考：[例: SDK for JavaScript で AdminCreateUser API を使用して作成されたユーザーの新しいパスワードの認証と設定](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/using-amazon-cognito-identity-user-pools-javascript-example-authenticating-admin-created-user.html)

```js:script.js

const region = 'us-east-2';
const userpoolid = 'us-east-2_aaaaaaaaa';
const clientid = 'xxxxxxxxxxx';

function callCognito() {
  document.getElementById('result').value = "";
  $('#message').empty();

  // 画面からID/PWを取得
  const username = document.getElementById('idTxt').value;
  const password = document.getElementById('pwTxt').value;

  const authenticationData = {
       Username : username,
       Password : password,
   };
  const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
  const poolData = {
     UserPoolId : userpoolid,
     ClientId : clientid
  };
  const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
  const userData = {
     Username : username,
     Pool : userPool
  };

  const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
  cognitoUser.authenticateUser(authenticationDetails, {
    onSuccess: function (result) { // 認証成功時の処理
       let accessToken = result.getAccessToken().getJwtToken();
       console.log(JSON.stringify(result));
       let idToken = result.idToken.jwtToken;
       console.log(idToken);
       $('#result').val(idToken);
    },

    onFailure: function(err) { // エラー発生時の処理
       console.log(JSON.stringify(err));
       $('#message').append(JSON.stringify(err));
    },

    newPasswordRequired: function(userAttributes, requiredAttributes) { // パスワード変更が必要なユーザーの、パスワードを強制変更
       cognitoUser.completeNewPasswordChallenge("test9999", {}, this)
       $('#message').append("パスワード変更が必要なユーザーのため、パスワードを「test9999」に変更しました\n");
    }
  });
}

```
値のセットが必要なのは先頭3行だけで、
`region`は、使用しているCognitoのリージョンをセットします。
`userpoolid`の値は、ユーザープールの、[全般設定]で確認できる、プールIDの値をセットします。
<img width="1213" alt="401.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/fb9ca67f-1da2-7606-ec73-797b18e79a0a.png">
`clientid`は、ユーザープールの、[アプリクライアント]で確認できる、アプリクライアントIDの値をセットします。
<img width="1212" alt="402.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/4bfa05d5-2777-1c0f-27bb-fe26c494f811.png">

### 🔶 フロントエンドの動作確認

以下は、githubに置いてあるサンプルですが、フロントエンドに、IDとPWを入力して[GET id token]ボタンをクリックすると、設定に問題が無ければIDトークンが表示されます。
<img width="809" alt="503.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/60a5fd9b-2aa7-5a75-b75a-d34bbfd9af2e.png">

# 🔷 API Gatewayでオーサライザーの作成
### 🔶 オーサライザーの作成

**1. API Gatewayで、新規にオーサライザーを作成する。**

API Gatewayで最初に作成したAPI画面を開き、左メニューの[オーサライザー]をクリックする。

[新しいオーサライザーの作成]をクリックする。
<img width="1025" alt="501.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/b6c7fda0-5984-6417-cb26-48827bf56d88.png">
オーサライザーの作成画面が表示されるので、以下の必要項目をセットして、[作成]をクリックします。
<img width="1083" alt="502.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/ad49f2ed-4100-6708-777c-84840abd975e.png">
⬛名前
任意の名称をセットします。

⬛タイプ
今回はCognitoで認可処理を行うので、「Cognito」を選択します。

⬛Cognitoユーザープール
先程作成したユーザープール名`samplePool`を選択します。

⬛トークンのソース
IDトークンは、APIリクエスト時のリクエストヘッダーにセットするが、その時のリクエストヘッダーのプロパティ名を指定します。
今回は`Authorization`とします。

⬛トークンの検証
エラーの原因になるので、今回のテストでは設定しません。

**2. オーサライザーのテストを行う。**
API Gatewayのオーサライザーの画面で、[テスト]をクリックする。
<img width="1082" alt="504.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/35819ec3-4a18-9338-9e69-71d323863f1c.png">
テストダイアログが表示されるので、「認可トークン」に値をセットして、[テスト]ボタンをクリックします。
レスポンスとしてユーザー情報が返ってきたら成功です。
<img width="606" alt="505.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/2b70d8d9-1ed7-9d9d-262f-ba84c02554c4.png">

⬛認可トークン
フロントエンドの動作確認で取得した、ユーザーのIDトークンをセットします。

### 🔶 エンドポイントにオーサライザーを設定

API Gatewayの左メニューの[リソース]をクリックして、任意のエンドポイント（今回はルートのGETメソッド）をクリックして、[メソッドリクエスト]をクリックします。
<img width="1368" alt="506.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/e0493ed7-d3aa-c55e-3144-966684c9bbe8.png">
認可の値に、先程設定したオーサライザーを設定する。
<img width="1101" alt="507.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/23c0c9fe-dec2-5a13-41d5-2abbc6fd7b00.png">
オーサライザーを設定したら、[アクション] - [APIのデプロイ]をクリックして、APIをデプロイする。
<img width="1101" alt="508.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/8eb26916-edcb-8304-bcd3-2ce962ed42eb.png">

これで全ての設定が終了です。

# 🔷 動作テスト
まずは、オーサライザーによる認可の設定を行ったエンドポイントに、リクエストを送ります。
<img width="1084" alt="601.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/eb668ad2-d32f-e244-ec9a-e134829255e4.png">
すると、IDトークンが無いため、レスポンスとして**`401 Unauthorized`**が返ってきます。
<img width="878" alt="602.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/30cad124-6d1c-eea3-da54-82c8a855197c.png">
次に、リクエストヘッダーに`Authorization`ヘッダーを追加して、IDトークンをセットしてリクエストを送ると、認可が通り、**`200 OK`**のレスポンスが返ってきます。
<img width="878" alt="603.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38099/02aef78c-92ec-a36d-9dd7-e9ed90f60284.png">

# 🔷 まとめ
以上がCognitoを使ったAPIのオーサライザーの設定の手順です。  
オーサライザーの動きから理解する所から始めると、Cognitoの使い方やフロントエンド側の処理の流れなど、覚える事が多くてかなり苦戦すると思います。

それでも頑張って一通り組めば、眺めるだけで認証・認可の仕組みが理解もできるし、これを自分で全て組もうとした時の難易度の高さも理解できるし、オーサライザーの仕組みを利用する方が圧倒的に簡単だという事がわかると思います。

また、オーサライザーはCognitoを使う以外にも、Lambdaで全て自作したり、Lambdaと[Auth0](https://auth0.com/docs/integrations/aws-api-gateway/custom-authorizers)のような外部サービスを組み合わせて構築する事もできるので、色々と試してみてください。



# 🔷 参考

* [ユーザープール認証フロー](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html)
* [Amazon Cognito ユーザープール に JavaScript アプリを追加する](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/setting-up-the-javascript-sdk.html)
* [Cognitoから払い出されたIdTokenをAPI Gateway カスタムオーサライザーのLambda(Python3.6)で検証する方法](https://dev.classmethod.jp/cloud/aws/verify_cognit_idtoken_by_apig_custom_auth/)
* [Cognitoに心のなかで謝罪しながら認証を通す
](https://qiita.com/shinichi-takahashi/items/bbfa7d505c45e2986f91)