APIGatewayのIAM認証付きのAPIをJavascriptから叩く

#実現したいこと
APIGatewayで認証を"AWS_IAM"に設定したAPIをブラウザからJavascriptで叩きます。これにより、CognitoのUnauthUserとAuthUserでアクセスできるAPIを制御することができます。

#やること
分かりやすいように順を追ってやっていきます。

1. Lambdaで簡単なFunctionを作成
2. APIGatewayで上記Functionを呼び出すAPIを作成する
3. 作成したAPIのCORSを有効化する
3. APIをデプロイし、API用のJavascript SDKを作成し、ダウンロードする
4. 認証なしでAPIを叩くJavascriptのコードを書く（ここで一度テストし、うまくいくことを確認）
5. APIの認証を"なし"→"AWS_IAM"に設定する（ここで一度テストし、アクセスが拒否されることを確認）
5. Cognito Identity Poolを作成
6. APIの実行を許可するIAMを作成し、UnauthUserのRoleにアタッチする
7. Cognito UnauthUserのCredentialでAPIを叩くJavascriptのコードを書く（完成）

##Lambdaで簡単なFunctionを作成
「Lambda」で"Create Function"> 「Select BluePrint」で"Blank Function"を選択>「Configure triggers」はそのままで"Next"

この画面では、関数の中身は編集しません。"FunctionName"に適当な関数名（myHelloWorldFuncなど）をつけ、下記の"Lambda function handler and role"のところで"Create a custom role"を選択します。
![スクリーンショット 2016-12-26 23.48.31.png](https://qiita-image-store.s3.amazonaws.com/0/86468/30069010-2999-e592-d758-577b0e842a6d.png)

すると別タブでIAMが開くので、"新しいIAMロールの作成"を選択して、そのまま右下の"許可"をクリックします。（すると、「lambda_basic_execution」という必要最低限のRoleが生成され、作成中のLambda Functionに適用されます。）

![スクリーンショット 2016-12-26 23.49.02.png](https://qiita-image-store.s3.amazonaws.com/0/86468/7af158bb-c476-ab76-e8d0-c1972055331d.png)

Functionの設定画面に戻ったら、"Lambda function handler and role"にさきほどの「lambda_basic_execution」が適用されていることを確認し、右下のOKをクリックします。

これで、実行すると「Hello World」とだけ返してくるLambda関数が完成しました。

##APIGatewayで上記Functionを呼び出すAPIを作成する
「APIGateway」で"APIを作成"
![スクリーンショット 2016-12-26 23.59.16.png](https://qiita-image-store.s3.amazonaws.com/0/86468/f9354fab-0c97-c497-e43b-e6343d88d3e3.png)

"アクション">"リソースの作成"

![スクリーンショット 2016-12-26 23.59.28.png](https://qiita-image-store.s3.amazonaws.com/0/86468/ee657889-737d-107a-c23f-950e958c7426.png)
ここでは「test」というリソースを作成します。
![スクリーンショット 2016-12-26 23.59.48.png](https://qiita-image-store.s3.amazonaws.com/0/86468/a95950af-79cd-34e7-dd12-e88f52ff31ef.png)

"アクション" > "メソッドの作成"

![スクリーンショット 2016-12-27 0.00.08.png](https://qiita-image-store.s3.amazonaws.com/0/86468/7e248c4a-eb61-17f8-2227-8c44fae24b6d.png)

ここでGetメソッドを作成します。（チェックマークをクリックして確定するのを忘れずに）
![スクリーンショット 2016-12-27 0.00.19.png](https://qiita-image-store.s3.amazonaws.com/0/86468/fa2385e1-4f17-9a00-507e-12fb97efa4b3.png)

セットアップの画面では、Lambda関数を選択し、さきほど作ったmyHelloWorldFunctionのリージョンを選択して、myHelloWorldFunctionを設定し、"保存"をクリックします。
![スクリーンショット 2016-12-27 0.00.42.png](https://qiita-image-store.s3.amazonaws.com/0/86468/3987a1af-3a66-b088-6d6a-63005c955f60.png)

Getメソッドが作成できました。

![スクリーンショット 2016-12-27 0.05.49.png](https://qiita-image-store.s3.amazonaws.com/0/86468/0b06ff5a-a26b-de90-acc1-414ab7fe6e30.png)

##作成したAPIのCORSを有効化する
"アクション" > "CORSの有効化"をクリック
![スクリーンショット 2016-12-27 0.09.52.png](https://qiita-image-store.s3.amazonaws.com/0/86468/40cfb31b-eff5-a796-192a-80e34b82abdd.png)

メソッドのGETにチェックが入っていることを確認し、それ以外の設定はいじらずに"CORSを有効にして既存のCORSヘッダーを置き換え"をクリック
![スクリーンショット 2016-12-27 0.10.06.png](https://qiita-image-store.s3.amazonaws.com/0/86468/f5913066-133b-85b0-74f4-45138d7fc67d.png)

CORSが有効化され、GETメソッドの下にOPTIONメソッドが追加されていればOKです。
![スクリーンショット 2016-12-27 0.10.22.png](https://qiita-image-store.s3.amazonaws.com/0/86468/16869705-6fa0-cb8c-99e6-290d178ddab4.png)

##APIをデプロイし、API用のJavascript SDKを作成し、ダウンロードする
testリソースが選択された状態で、"アクション" > "APIのデプロイ"をクリック
![スクリーンショット 2016-12-27 0.15.04.png](https://qiita-image-store.s3.amazonaws.com/0/86468/405c9dbf-58a6-1ba5-ec4d-94ff20c5cfd1.png)

ここでは「dev」という名前の新しいステージを作成し、デプロイします。
![スクリーンショット 2016-12-27 0.15.21.png](https://qiita-image-store.s3.amazonaws.com/0/86468/cd2e2221-53cd-f246-aa71-62f67a12f68d.png)

「ステージエディター」で"SDKの生成"から、プラットフォーム"Javascript"を選択し、SDKの生成をクリックします。
![スクリーンショット 2016-12-27 0.18.13.png](https://qiita-image-store.s3.amazonaws.com/0/86468/7baecb8c-a270-62bf-6d39-a4bec5e0e157.png)

すると、SDKのファイルがダウンロードできます。

##認証なしでAPIを叩くJavascriptのコードを書く（ここで一度テストし、うまくいくことを確認）
![スクリーンショット 2016-12-27 0.29.18.png](https://qiita-image-store.s3.amazonaws.com/0/86468/a00d1065-b051-1629-e749-d8a49d5be439.png)

SDKのZIPを解答したら、中に適当なHTMLファイルを作成し、下記のコードを書きます。(ここではAPIGatewayTest.htmlというファイルを作成しています。)

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>API test</title>
  <!-- jQuery -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
  <!-- API用SDK -->
  <script type="text/javascript" src="lib/axios/dist/axios.standalone.js"></script>
  <script type="text/javascript" src="lib/CryptoJS/rollups/hmac-sha256.js"></script>
  <script type="text/javascript" src="lib/CryptoJS/rollups/sha256.js"></script>
  <script type="text/javascript" src="lib/CryptoJS/components/hmac.js"></script>
  <script type="text/javascript" src="lib/CryptoJS/components/enc-base64.js"></script>
  <script type="text/javascript" src="lib/url-template/url-template.js"></script>
  <script type="text/javascript" src="lib/apiGatewayCore/sigV4Client.js"></script>
  <script type="text/javascript" src="lib/apiGatewayCore/apiGatewayClient.js"></script>
  <script type="text/javascript" src="lib/apiGatewayCore/simpleHttpClient.js"></script>
  <script type="text/javascript" src="lib/apiGatewayCore/utils.js"></script>
  <script type="text/javascript" src="apigClient.js"></script>
</head>
<body>
<!-- HTML  -->
<button id="btn">ボタン</button>
<!-- Other Script -->
<script type="text/javascript">
var apigClient;
$(function(){
//API Clientを初期化
  apigClient = apigClientFactory.newClient();
});
$('#btn').click(function(){
//testリソースのGetメソッドを呼び出すので、.testGet()
  apigClient.testGet({}, {}, {})
  .then(function(result){
    // Add success callback code here.
    console.log(result);
  }).catch( function(result){
    // Add error callback code here.
    console.log(result);
  });
});
</script>
</body>
</html>
```

ボタンをクリックすると、APIの呼び出しが成功し、Lambdaから'HelloWorld'の文字列が返ってくることを確認します。
![スクリーンショット 2016-12-27 0.33.05.png](https://qiita-image-store.s3.amazonaws.com/0/86468/9787d83e-5b10-ee00-ec6a-fc3ea7d6cdb3.png)

##APIの認証を"なし"→"AWS_IAM"に設定する（ここで一度テストし、アクセスが拒否されることを確認）
ここで、作成したAPIの"メソッドリクエスト"に記載されている**ARN(Amazon Resource Name)**をコピーしておきます。（"arn:aws:execute-api:ap-notheast-xxxxxxxx/*/GET/test"という文字列です。）

作成したAPIのGetメソッドの"メソッドリクエスト"をクリック
![スクリーンショット 2016-12-27 0.37.56.png](https://qiita-image-store.s3.amazonaws.com/0/86468/01d3eb1a-b95a-7231-d533-247faaf7bc58.png)

認証の設定を"AWS_IAM"に変更します。
![スクリーンショット 2016-12-27 0.38.22.png](https://qiita-image-store.s3.amazonaws.com/0/86468/558525dc-8a66-92fb-3ff4-e0267c00e902.png)

APIを再びDevステージにデプロイします。
![スクリーンショット 2016-12-27 0.42.25.png](https://qiita-image-store.s3.amazonaws.com/0/86468/65f99d7d-a74f-cb6a-0514-8a7971e17b1b.png)

こうして再びボタンを押してAPIを叩いてみると、403エラーが返ってくることを確認します。認証をAWS_IAMに設定したため、アクセスが拒否されています。（CORSに問題があるというエラー文ですが、問題ありません。）
![スクリーンショット 2016-12-27 0.43.58.png](https://qiita-image-store.s3.amazonaws.com/0/86468/8d44efb2-4269-5970-e5d8-f6fde3a53bc4.png)

##Cognito Identity Poolを作成
「Cognito」で"Manage Federated Identities"をクリック > "Create Identity Pool"をクリック

次の画面では、Identity Poolの名前（ここでは"MyTestIdentity"）を入力し、**"Enable access to unauthenticated identities"にチェックを入れた上で**"Create Pool"をクリックします。

![スクリーンショット 2016-12-27 0.47.58.png](https://qiita-image-store.s3.amazonaws.com/0/86468/a11dd179-d640-9fbb-e937-af0a2eb0cc14.png)

次の画面で確認できる**"Identity Pool ID"**をコピーしておきます。

![スクリーンショット 2016-12-27 1.14.44.png](https://qiita-image-store.s3.amazonaws.com/0/86468/4d5c89ea-c418-b6ab-ef5a-860f2d56c6c8.png)


##APIの実行を許可するIAMを作成し、UnauthUserのRoleにアタッチする
「IAM」で"ロール"を開くと、さきほど"MyTestIdentity"というIdentity Poolを作成した時にCognito_MyTestIndentityUnauth_Roleという未認証ユーザー用のRoleと、Cognito_MyTestIndentityAuth_Roleという認証済みユーザー用のRoleが２つ作成されているのが確認できます。今回はCognitoの未認証ユーザーに対してAPIへのアクセス権限を与えるので、"Cognito_MyTestIndentity **Unauth** _Role"をクリックします。

"ロールポリシーの作成"をクリックします。
![スクリーンショット 2016-12-27 0.57.36.png](https://qiita-image-store.s3.amazonaws.com/0/86468/f643d35b-e86a-1e95-8952-5901db6fec5c.png)

"Policy Generator"にチェックを入れ、"選択"をクリックします。
![スクリーンショット 2016-12-27 0.58.15.png](https://qiita-image-store.s3.amazonaws.com/0/86468/1f6983ea-a6a8-d37b-9996-d48d68af5cba.png)

「アクセス許可の編集」では、下記のように設定します。

- 効果：許可
- AWSサービス：Amazon APIGateway
- アクション：Invoke
- Amazonリソースネーム(ARN)：先ほどコピーしたGETメソッドのARN

これらを設定し、"ステートメントを追加"をクリックします。

![スクリーンショット 2016-12-27 1.03.20.png](https://qiita-image-store.s3.amazonaws.com/0/86468/fa897e61-34ec-e989-d820-bd5f0992ec61.png)

下記のようになっていれば、OKです。"次のステップ"をクリックします。
![スクリーンショット 2016-12-27 1.03.31.png](https://qiita-image-store.s3.amazonaws.com/0/86468/b6defa0d-b4fc-f34e-2ea0-11fb7b9a9282.png)

次の確認画面では何もせず、"ポリシーの適用"をクリックします。
![スクリーンショット 2016-12-27 1.06.38.png](https://qiita-image-store.s3.amazonaws.com/0/86468/3500246c-cade-caf2-8ec8-be76b9664c9b.png)

これで、UnauthUserに対してAPIを実行する許可を与えることができます。

##Cognito UnauthUserのCredentialでAPIを叩くJavascriptのコードを書く（完成）

さきほど作成したHTMLファイルを、下記のように編集します。

- `<script src="https://sdk.amazonaws.com/js/aws-sdk-2.7.19.min.js"></script>`でAWS SDK for Javascriptを読み込み
- `AWS.config.credential`のパラメーターにさきほどコピーした**Identity Pool ID**を貼り付ける
- `AWS.config.credential.get()`によって得られるaccessKeyIdとsecretAccessKeyとsessionToken、及びregionをapigClientに設定して初期化する。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>API test</title>
  <!-- AWS SDK -->
  <script src="https://sdk.amazonaws.com/js/aws-sdk-2.7.19.min.js"></script>
  <!-- jQuery -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
  <!-- API用SDK -->
  <script type="text/javascript" src="lib/axios/dist/axios.standalone.js"></script>
  <script type="text/javascript" src="lib/CryptoJS/rollups/hmac-sha256.js"></script>
  <script type="text/javascript" src="lib/CryptoJS/rollups/sha256.js"></script>
  <script type="text/javascript" src="lib/CryptoJS/components/hmac.js"></script>
  <script type="text/javascript" src="lib/CryptoJS/components/enc-base64.js"></script>
  <script type="text/javascript" src="lib/url-template/url-template.js"></script>
  <script type="text/javascript" src="lib/apiGatewayCore/sigV4Client.js"></script>
  <script type="text/javascript" src="lib/apiGatewayCore/apiGatewayClient.js"></script>
  <script type="text/javascript" src="lib/apiGatewayCore/simpleHttpClient.js"></script>
  <script type="text/javascript" src="lib/apiGatewayCore/utils.js"></script>
  <script type="text/javascript" src="apigClient.js"></script>
</head>
<body>
<!-- HTML  -->
<button id="btn">ボタン</button>
<!-- Other Script -->
<script type="text/javascript">
var apigClient;
$(function(){
  //AWS Credentialの初期化
  AWS.config.region = 'ap-northeast-1';
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
      IdentityPoolId: 'ap-northeast-XXXXXXXXXXXXXXXXXXXXXXX' //Identity Pool Idを貼り付ける(ARNではない。)
  });
  AWS.config.credentials.get(function(err){
    if(!err){
      //API ClientにaccessKey,secretKey,sessionToken,regionを設定し、初期化します。
      apigClient = apigClientFactory.newClient({
        accessKey: AWS.config.credentials.accessKeyId,
        secretKey: AWS.config.credentials.secretAccessKey,
        sessionToken: AWS.config.credentials.sessionToken,//大事
        region: 'ap-northeast-1'//大事
      });
    }else{
      console.log(err);
    }
  });
});
$('#btn').click(function(){
  apigClient.testGet({}, {}, {})
  .then(function(result){
    // Add success callback code here.
    console.log(result);
  }).catch( function(result){
    // Add error callback code here.
    console.log(result);
  });
});
</script>
</body>
</html>

```

これでボタンをクリックすると、APIの呼び出しに成功し、Lambdaから'Hello World'の文字列が返ってくることを確認できます。

![スクリーンショット 2016-12-27 1.20.01.png](https://qiita-image-store.s3.amazonaws.com/0/86468/6431ba0a-dbf5-5d17-53b3-553f778a23f4.png)


#ポイント
- CORSを有効化したり認証を変更したりなど、APIの設定をいじった後は、APIをデプロイするのを忘れない。
- APIGatewayで生成したJavascript用のSDKは、超絶面倒くさいSignature V4の署名付きリクエストをやってくれる。
- apigClientにアクセスキーとシークレットキーを設定する時、sessionTokenとregionはドキュメントではオプションとなっているが、ここを設定しないと動かないので全て設定する。（ここでだいぶハマった。）

##参考URL
[API Gateway リソースの CORS を有効にする](http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-cors.html)
[API 実行アクセス権限の IAM ポリシーの例](http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-iam-policy-examples-for-api-execution.html)
[API Gateway コンソールを使用して API の SDK を生成する](http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-generate-sdk-console.html)
[API Gateway で生成した JavaScript SDK を使用する](http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-generate-sdk-javascript.html)
[aws-api-gateway-client](https://www.npmjs.com/package/aws-api-gateway-client)