API GatewayでCognitoの未認証ユーザへアクセスを許可する

## やりたいこと

Cognitoユーザプールの認証済みユーザ、未認証ユーザだけが呼び出せるAPI Gatewayを作成する。
APIの直接呼び出しは許可しない。

認証プロバイダは使用しないけど、特定のアプリのゲストアクセスも使用できるAPIを作成します。

## 前提
Xcode:11.4
Amplifyが使用可能
AWSMobileClient:2.13.0
AWSAPIGateway:2.13.0

## AWS構成図

![Untitled Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59457/86d50c4a-1576-ed2d-cdeb-9d709ceadc64.png)


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

Amplifyを使用してユーザを作成します。

Xcodeプロジェクトディレクトリでコンソールからamplifyを使ってCognitoユーザプールを作成。

```
amplify add auth
```

内容は任意で作成して

```
amplify push
```

としてCognitoのユーザ作成を行います。

## API Gatewayの設定

1.RESTでAPIを作成。Lambdaファンクションの実装はご自由に。
2.コンソールで作成したAPIを選択。
3.メニューから「リソース」を選択し、メソッド（GETなど）を選択。
4.メソッドリクエストの「認可」を"AWS_IAM"を選択。
5.デプロイしてSDKも生成しておく。

## ロールにポリシーを設定

1.Cogniteのコンソールに移る。
2.Amplifyで生成したプールIDを選択。
3.フェデレーティッドアイデンティティの画面から右上にあるIDプールの編集を選択。
4.認証されていないIDセクションから「認証されていない ID に対してアクセスを有効にする」をチェック
5.同じ画面で認証されていないロール、されているロールが表示されているので覚えておく。

## ロールにポリシーを追加

1.IAMに移動
2.Cognitoの認証、未認証のロールを選択しポリシーをアタッチする。
以下のようなポリシーでAPI Gatewayの呼び出しを許可する。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "execute-api:*"
            ],
            "Resource": [使いたいAPI GatewayのARN名]
        }
    ]
}
```

## Xcode側の実装

結構ハマりどころ。

API Gatewayで生成したSDKをプロジェクトにインポート。BridgingHeaderも。
podでAWSMobileClientとAWSAPIGatewayをインストールする。バージョンは2.13.0を使用した。

```:Podfile
$awsVersion = '~> 2.13.0'
pod 'AWSMobileClient', $awsVersion
pod 'AWSAPIGateway', $awsVersion
```

AppDelegateに以下を追加。

```swift:AppDelegate.swift
import AWSMobileClient

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
    // AWSMobileClientを初期化
    AWSMobileClient.sharedInstance().initialize { (userState, error) in
        guard error == nil else {
            print("Error initializing AWSMobileClient. Error: \(error!.localizedDescription)")
            return
        }
        print("AWSMobileClient initialized.")
    }

    return true
}
```

APIを呼び出す処理はこんな感じ。

```swift
import AWSAPIGateway
import AWSCognitoIdentityProvider

func testGetFunc(){
    let credentialsProvider = 
    AWSCognitoCredentialsProvider(regionType:.リージョンのenum,  identityPoolId:"プールID")
    let configuration = AWSServiceConfiguration(region:.リージョンのenum, credentialsProvider:credentialsProvider)
    AWSServiceManager.default().defaultServiceConfiguration = configuration

    let client = [API Gatewayで生成したSDKクラス].init(configuration: AWSServiceManager.default().defaultServiceConfiguration)

    client.rootGet().continueWith { (task) -> Any? in
        if let error = task.error {
            print("Error occurred: \(error)")
            // エラー時の処理
            return nil
        }

        // 正常時の処理
        if let result = task.result {
            // task.resultにはSDKクラスになっているので好きなように処理する。
            result.hogeList?.forEach({ (item) in
                print(item.hogeValue)
            })
        }
        return task
    }
}
```

だいぶ端折りましたがこんな感じです。

## 確認する

ブラウザから、他のアプリからのアクセスがNG。
今回作成したアプリからのアクセスは正常に戻り値が取得可能になっていることを確認します。

## ハマったところ

- API Gatewayで生成したSDKクラスが全然動かなかった。
- AWSMobileClient、AWSAPIGatewayは最新版はAPIが変わっているので使い方がわからず。2.13.0にとどめた
- API GatewayのオーソライザーでCognitoを使うのかと思っていたが違っていた。