Cognito Identity Pool を用いて、AWS SDK v3 の認証を行う。

Level 400 / AWS SDK v3 + Cognito Identity Pool + IAM Role + Permission
# 概要

この記事は、AWS SDK を v2 から v3 へ移行する際の方法を記載します。
要件上の問題から、 AWS Amplify は利用していません。

クライアントサイドで Cognito UserPool で認証し、 Cognito Identity Pool で認可して AWS Credential を発行し、AWS SDK を直接操作しています。
そのため、API Gateway 側で認可するなどの　サーバーサイドで実行する際は別の方法になります。

また、  IdToken の取得方法は v2/v3 で差がないので記載しません。

- see also
    - [amazon-cognito-identity-js](https://www.npmjs.com/package/amazon-cognito-identity-js)

# 概要図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240771/8e49b8e5-d7a7-d514-1ca6-0fe402d46225.png)

# TL;DR
DynamoDB の Client を生成しているのは、今回必要だったため。
任意のクライアントで同様の方法です。

#### Identity ID が不要な場合。

```js
import { DynamoDBClient } from "@aws-sdk/client-dynamodb"
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity"

const region = 'ap-northeast-1'
const dynamodb = new DynamoDBClient({
    region,
    credentials: fromCognitoIdentityPool({
        client: new CognitoIdentityClient({ region }),
        identityPoolId: '{identity pool id}',
        logins: {
            [userPoolId]: user.getIdToken().getJwtToken()
        }
    })
})
```

#### Identity ID が必要な場合
```js
import { CognitoIdentityClient, GetIdCommand } from "@aws-sdk/client-cognito-identity"
import { DynamoDBClient } from "@aws-sdk/client-dynamodb"
import { fromCognitoIdentity } from "@aws-sdk/credential-provider-cognito-identity"

const region = 'ap-northeast-1'
const cognito = new CognitoIdentityClient({region})
const identity = await cognito.send(new GetIdCommand({
    IdentityPoolId: '{identity pool id}',
    Logins: {
        [userPoolId]: user.getIdToken().getJwtToken()
    }
}))
​
if (!identity.IdentityId) {
    throw new Error('Auth error')
}
const dynamodb = new DynamoDBClient({
    region,
    credentials: fromCognitoIdentity({
        client: new CognitoIdentityClient({ region }),
        identityId: identity.IdentityId,
        logins: {
            [userPoolId]: user.getIdToken().getJwtToken()
        }
    })
})

```

# AWS SDK v3 の Credentials 
## Credentials の取り扱いについて

AWS-SDK v2 / v3 の大きな変更点に、Credentials の取り扱いの変更があると考えます。

v2 の場合、AWS SDK の config を変更することにより、 region や credentials を変更していました。

サンプル: 
```js
import * as AWS from 'aws-sdk'

AWS.config.region = 'ap-northeast-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials(...);
```


v3 の場合は、 Client をインスタンス化する際に、credentials に Provider を渡すことにより認証します。
これは、今回私が行っているような複数の認証情報を利用する場合に、そのスコープを局所化するのにとても有意です。

サンプル

```js
import { DynamoDBClient } from "@aws-sdk/client-dynamodb"
import { fromCognitoIdentity } from "@aws-sdk/credential-provider-cognito-identity"

const dynamodb = new DynamoDBClient({
    region,
    credentials: fromCognitoIdentity(...)
})
```

Provider は用途に合わせて複数のものが用意されています。
これらの Provider を credentials に渡すことにより、認証が行なえます。
今までのように、Credentials の値を、開発者がやり取りする必要もなくなり、そこもメリットと考えています。

- Providers
    - fromCognitoIdentity()
    - fromCognitoIdentityPool()
    - fromTemporaryCredentials()
    - fromWebToken()
    - fromContainerMetadata()
    - fromInstanceMetadata()
    - fromIni()
    - fromEnv()
    - fromProcess()
    - fromTokenFile()
    - fromSSO()
    - fromNodeProviderChain()

- see also
    - [@aws-sdk/credential-providers](https://www.npmjs.com/package/@aws-sdk/credential-providers)

## Cognito Identity Pool の取り扱い。

Cognito Identity Pool で単純に Credentials を利用する場合、 `fromCognitoIdentityPool` を利用するだけです。

こちらは、Web 上で多くのサンプルが見つかるため、特にここでは詳細は記載しません。

```js
import { DynamoDBClient } from "@aws-sdk/client-dynamodb"
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity"

const region = 'ap-northeast-1'
const dynamodb = new DynamoDBClient({
    region,
    credentials: fromCognitoIdentityPool({
        client: new CognitoIdentityClient({ region }),
        identityPoolId: '{identity pool id}',
        logins: {
            [userPoolId]: user.getIdToken().getJwtToken()
        }
    })
})
```

ここで問題になるのは、 プログラムがユーザーの IdentityID を必要とする場合です。
たとえば、S3 や DynamoDB にユーザー自身の Cognito Identity Pool IdentityID の領域に書き込む場合は、自身の IdentityID を必要とします。

具体的には、IAM Role に `${cognito-identity.amazonaws.com:sub}` を利用する場合で、DynamoDB では以下のようなロールが該当します。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:PutItem",
      ],
      "Resource": [
        "arn:aws:dynamodb:*:123456789012:table/MyTable"
      ],
      "Condition": {
        "ForAllValues:StringEquals": {
          "dynamodb:LeadingKeys":  ["${cognito-identity.amazonaws.com:sub}"]
        }
      }
    }
  ]
}
```

この場合、fromCognitoIdentityPool ではユーザー自身の IdentityID を取得できないため、先に自身の IdentityID を GetID で取得し、その後 fromCognitoIdentity で Credentials を取得することが必要です。

- see also 
    - [GetID API](https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetId.html)

```js
import { CognitoIdentityClient, GetIdCommand } from "@aws-sdk/client-cognito-identity"
import { DynamoDBClient } from "@aws-sdk/client-dynamodb"
import { fromCognitoIdentity } from "@aws-sdk/credential-provider-cognito-identity"

const region = 'ap-northeast-1'
const cognito = new CognitoIdentityClient({region})
const identity = await cognito.send(new GetIdCommand({
    IdentityPoolId: '{identity pool id}',
    Logins: {
        [userPoolId]: user.getIdToken().getJwtToken()
    }
}))
​
if (!identity.IdentityId) {
    throw new Error('Auth error')
}
const dynamodb = new DynamoDBClient({
    region,
    credentials: fromCognitoIdentity({
        client: new CognitoIdentityClient({ region }),
        identityId: identity.IdentityId,
        logins: {
            [userPoolId]: user.getIdToken().getJwtToken()
        }
    })
})

```

この場合、　`identity.IdentityId` としてユーザーの IdentityId が取得できるため、あとは通常通りアイテムの操作が可能となります。

# 雑記

この情報ですが、日本語はさておき、英語にもほとんど情報が出ていません。
今回の要件として Amplify が利用できず、また、AWS SDK v3 を利用したため、公式のサンプルもアップデートがされておらず参考になりませんでした。

複数の情報を総合し、開示されている API などから導いたのが上記の利用方法となります。

まだまだ、 AWS SDK v3 はその利用法について情報が不足している段階とは思いますが、インスタンスとして局所化できたり、コードサイズが小さくなるなどのメリットが多いため、 ~~人柱~~ 積極的に利用していきたいです。

以上