CloudFrontを使ってTerraMap APIにリクエストする

## この記事を作成した背景
外部APIにリクエストする際、APIキーをセットするタイプのAPIだとJavaScriptから直接リクエストしたときにAPIキーが見えてしまうためセキュアではなく、無駄なリクエストを消費する懸念があります。
サーバーサイドプログラムを実装し、APIキーをセットしてプロキシサーバーとして利用することもプログラム実装や環境構築の手間がかかります。
地図システム開発支援Web APIであるTerraMap APIはAPIキーをセットするタイプのAPIであり、サーバーサイドプログラムの実装や自前でインフラ構築することなく、容易にTerraMap APIにリクエストする方法を考えてみました。

## 概要説明
今回は[CloudFront](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/Introduction.html)をリバースプロキシとして利用して[TerraMap API](https://www.mapmarketing.co.jp/terramap-api?grid=qiita&utm_source=qiita.com&utm_medium=referral&utm_campaign=qiita_cloudfront_d28ed52f819e30f8bb94_01)にリクエストし、エリア取得APIと統計データ一覧取得APIのレスポンスを取得するようにしてみました。CloudFrontにはJavaScriptでアクセスするようにします。

**JavaScript　→　CloudFront　→　TerraMap API**

## CloudFrontの設定
はじめにCloudFrontの設定を行っていきます。
1. CloudFrontの初期画面で「CloudFrontディストリビューションを作成」を押下します。![スクリーンショット 2023-01-16 170711.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916977/271eabd4-f1eb-00d8-c261-e2bb8afb4f8c.jpeg)

2. 次の画面でディストリビューションを設定します。
設定を一つずつ見ていきます。
オリジンドメインはTerraMap APIのドメインを指定します。※画像のドメインはダミーです。
プロトコルをHTTPSのみとしHTTPSを使用してオリジンにアクセスするようにしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916977/3516c907-3b58-18a9-c387-8ca87e4b5029.png)
オリジンパスはTerraMap APIのパスを指定します。「/api」と入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916977/553ea9fc-d613-fd92-4c65-299eff102d57.png)
今回はヘッダーにAPIキーを設定するので「ヘッダーを追加」を押下し、ヘッダー名と値を入力しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916977/9c3e263f-e5b8-5072-de00-2e3e84c62c12.png)
許可するメソッドを指定します。今回はすべてのHTTPメソッドを許可にしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916977/f6b752d5-830f-c5de-074c-a73e939978e1.png)
TerraMap APIにはGETメソッドでリクエストするAPIもあり、クエリ文字列の受け渡しも必要となります。クエリ文字列をAPIに受け渡すにはキャッシュポリシーでキャッシュを有効にする必要があります。
キャッシュを適切に設定してオリジンへのリクエストを抑制できることがCloudFrontの魅力の一つですが、今回はキャッシュを目的としていないのでTTLはすべて最小限の1としました。TTLの設定は用途によりますが、TerraMap APIのデータは修正や更新があることを考慮して短く設定してください。
そしてキャッシュポリシー設定のクエリ文字列は「すべて」を選択しておきます。
![screencapture-us-east-1-console-aws-amazon-cloudfront-v3-home-2023-01-16-18_46_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916977/e7c7b464-5103-c851-b8a3-60f739e680b9.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916977/618c3423-363b-3ceb-dc0f-a51642cfbd50.png)
異なるオリジンからアクセスする場合、レスポンスヘッダーポリシーを作成して[CORS](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)を有効にします。
今回、その他の項目はデフォルトのままにしました。「ディストリビューションを作成」を押下してCloudFrontの設定は完了です。

## CloudFrontのセキュリティに関する設定　
APIキーの隠蔽はできましたが、JavaScriptからのリクエストだとURLが見え第3者にリクエストを消費される懸念が残ります。
今回は設定を行いませんが、以下のような方法でセキュリティを保つことができます。
- CloudFrontで署名つきURLやCookieを活用してアクセス制限する
- CloudFront FunctionやLambda@Edgeを活用して独自ロジックによる制御を行う
- WAFを活用してコンテンツへのアクセスを制御する
- Cognitoを使用して特定の人だけがアクセスできるようにする

> 参考情報
> [署名付き URL と署名付き Cookie を使用したプライベートコンテンツの提供](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html)
> [関数を使用してエッジでカスタマイズ](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/edge-functions.html)
> [AWS WAF を使用してコンテンツへのアクセスの管理](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/distribution-web-awswaf.html)
> [Lambda@Edgeを利用してCloudFrontにCognito認証をかける](https://zenn.dev/gsy0911/articles/1cd558034afc67b52a46)

## JavaScriptからCloudFrontにアクセスする
続いてさきほど設定したCloudFrontにJavaScriptからアクセスするようにします。
コードは以下にように作成します。
今回はTerraMap APIのエリア取得APIと統計データ一覧取得APIにリクエストします。
エリア取得APIは指定地点から半径1km円と共有部分がある町丁目のポリゴンと令和2年国勢調査（2020年国勢調査）の人口総数を取得します。統計データ一覧取得APIは2021バージョン、町丁目、令和2年国勢調査（2020年国勢調査）に合致する統計データ一覧を取得します。
```javaScript:index.js
const requestBody = {
  layer_id: "00104",
  area_type: "circle",
  center_lat: 35.681236,
  center_lng: 139.767125,
  radius: [1000],
  stats: [
    {
      stat_id: "001012000",
      stat_item_id: [15776],
    },
  ],
  output: "polygon,data",
};

// さきほど設定したCloudFrontのディストリビューションドメイン名を指定します。
const url = `https://distribution_domain_name`;

function fetchAreaApi() {
  fetch(`${url}/area`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(requestBody),
  })
    .then((response) => response.json())
    .then((jsondata) => {
      const result = document.querySelector("#result");
      result.innerText = JSON.stringify(jsondata);
    })
    .catch((error) => {
      console.log(error);
    });
}

const params = {
  // 渡したいパラメータをJSON形式で書く
  layer_id: "00104",
  layer_version: "202101",
  stat_id: "001012000",
};
const queryParams = new URLSearchParams(params);

// 統計データ取得
function fetchStatsApi() {
  fetch(`${url}/stats?${queryParams}`, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  })
    .then((response) => response.json())
    .then((jsondata) => {
      const result = document.querySelector("#result");
      result.innerText = JSON.stringify(jsondata);
    })
    .catch((error) => {
      console.log(error);
    });
}
```

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <title>TerraMap API・CloudFrontリクエスト</title>
  </head>
  <body>
    <script src="./index.js"></script>
    <h1>TerraMap API・CloudFrontリクエスト</h1>
    <button onclick="fetchAreaApi()">エリア取得</button>
　　<button onclick="fetchStatsApi()">統計データ一覧取得</button>
    <h2>結果表示</h2>
    <div id="result"></div>
  </body>
</html>
```
「エリア取得」を押下するとエリア取得APIのレスポンスを確認することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916977/a34e5881-d114-4cdd-a1dc-66af18f44f25.png)
「統計データ一覧取得」を押下すると統計データ一覧取得APIのレスポンスを確認することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916977/665cd356-be6c-204f-3ec0-18c75c147c27.png)