はじめてのAWS Cognito！！

:santa:[「Amazon Web Services Advent Calendar 2017」20日目の記事です](https://qiita.com/advent-calendar/2017/aws)

# はじめに
今回、Advent Calendarへの投稿が「Qiita」デビューです。
あと、どうでも良い情報として、本日は、私の誕生日:birthday:です（四十路です・・）。

先日、[「AWS SDK for JavaScript」](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/welcome.html)を使用して[Cognito](
https://aws.amazon.com/jp/cognito/ "Cognito")を使用したのですが、諸々の用語や抑えどころが分からずに悩んだ点がいくつかあったので、*「はじめてのAWS Cognito」*と題してポイントをまとめてみました。

私自身も手探り感満載だったので、コメントやご助言頂けると嬉しいです！
**Cognito Sync**については、まとめてないので、こちらを期待されている方は、スルーしてくださいw。

## １．そもそもCognitoとは？

・ AWSが提供する**認証・認可**基盤です。通常は、[IAM](
http://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/introduction.html "IAM")ユーザーのアクセスキーや、インスタンスに対するIAMロールを使用して認証します。

## ２．なぜCognitoを使うのか？
・クライアントアプリ（SPAやスマホのネイティブアプリ）より直接AWSリソースへアクセスしたい要件が増えてきている中で、扱いに困るのが**IAMユーザーのアクセスキー**や**シークレットキー**の扱いです。

特に、SPAのようなJavaScriptを使用したアプリケーションは、容易にブラウザのデバックツールより抜き取ることが可能なため、セキュリティ上問題があります。
また、仮になんらかの方法で、ファイルを難読化したとしても、永続的に権限を付与し続けることになるため、健全な運用ではありません。

そこで、Cognitoを使用して**クライアントアプリからの認証を行い、IAMロールを使用して権限を払い出す**仕組みを実現しています。


## ３．用語について
Cognitoで使用される用語を私なりにまとめてみました。

- **フェデレーティッドアイデンティティ**
どのようにして、認証を行うかの設定を行います。**ソーシャルIDプロバイダー**（FB、Twitter etc）の指定や、認証後の使用可能なAWSリソースのIAMロール設定等を行います。
　

- **ユーザプール**
「フェデレーティッドアイデンティティ」で作成されたIDプールに対してユーザの紐付けを行います。その際に使用するのが、ユーザプールです。
どのアプリケーションに対して、どのような認証を行い（メール認証の有無や、MFA等）、パスワードポリシーの設定や、ユーザのアクティベーション等もあります。

---------------------------------------
## ４．どうやって使うのか
**AWS SDK for JavaScript**を使用して、Cognito経由でDynamoDBへアクセスしてみます。

### 未認証でとりあえず使ってみる
まずは、フェデレーティッドアイデンティティを新規に作成して、未認証でも権限の払い出しをしてもらい、どんな感じになるか試します。

**１． フェデレーテッドアイデンティティを作成する**

- 「フェデレーテッドアイデンティティの管理」-> 「新しいIDプールの作成」
  - ID プール名： 今回は、demo_cognito_unauthenticated
  - 「認証されていない ID に対してアクセスを有効にする」をチェックし、「プールの作成」
  - 「Your Cognito identities require access to your resources」は「許可」

<img width="1001" alt="スクリーンショット 2017-12-07 0.17.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/20512/b6308bc1-0789-b9fb-92e6-4a09e5f7a8cc.png">

-------

**２． IAMロールの編集**
DynamoDBへのアクセス許可の設定を追加します。
**IAM**から、**ロール**をクリックすると、以下のようなIAMロールが作成されて
いるはずです。

~~~python
oneClick_Cognito_[ID プール名]Auth_Role_XXXXXXXXXXX
oneClick_Cognito_[ID プール名]UnAuth_Role_XXXXXXXXXXX
~~~

編集するロールを選択 -> 「ポリシーの編集」で以下のようにDynamoDBの権限を追加してください。
ここでは、
 `oneClick_Cognito_demo_cognito_unauthenticatedAuth_Role_XXXXXXXXXXX`
を指定します。

~~~yaml
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*",
                "cognito-identity:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {   // 追加します。（コメントは追加時には削除しましょう。書式チェックで怒られます）
            "Effect": "Allow",
            "Action": [
                "dynamodb:BatchGetItem",
                "dynamodb:BatchWriteItem",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXXX:table/demo_cognito_table", // DynamoDBのARNを指定
            ]
        }		
    ]
}
~~~

**3． DynamoDBにアクセスする**

[「AWS SDK for JavaScript」](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/welcome.html)を使用すると、`IdentityId`を取得することが出来ます。こちらのIDは、SDKがよろしく保持してくれるので、こちらで管理する必要はありません。

poolIdには、「フェデレーテッドアイデンティティ」のプールIDを指定します。プールIDにはユーザプールIDもあるので、ちょっと紛らわしいですね・・

以下は、DynamoDBのテーブルからのデータ取得例です。

```js

var aws = require('aws-sdk');
var config = {
    poolId: 'ap-northeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
    region: 'ap-northeast-1'        
};

aws.config.region = config.region;
aws.config.credentials = new aws.CognitoIdentityCredentials(
                                {IdentityPoolId: config.poolId});
        
aws.config.credentials.get(function(err) {
    if (!err) {
        console.log("Cognito Identify Id: " + aws.config.credentials.identityId);                

        // DynamoDBへ接続
        var dynamoDB = new aws.DynamoDB();
        var docClient = new aws.DynamoDB.DocumentClient({service:dynamoDB});

        var params = {
            TableName : 'demo_cognito_table',
            Key: {
                'id': '1'
            }
        };  
        
        docClient.get(params, function(err, data) {
            if (!err){
                console.log(data);
            } else {
                console.log(err);
            }
        });        
    }
}); 
```

実行するとDynamoDBにアクセスして、データ取得できました。:v_tone1:

```
$ node unauth.js 
Cognito Identify Id: ap-northeast-1:xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
{ Item: { id: '1', username: 'katekichi' } }
```
DynamoDB周りの[API仕様](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html)は別途ご確認ください。

未認証アクセスの用途としては、認証画面が存在しないアプリケーションで直接クライアントからAWSリソースに対してアクセスした場合等に使用することになると思います。

### Cognitoユーザによる認証
**ソーシャルIDプロバイダー**（FB、Twitter etc）で認証することが実際の用途としては多いかなと思いますが、今回は、Cognitoの認証プロバイダを使用してみます。

Cognitoの認証プロバイダは、メールやSMSを使用したアクティベーションも可能です（アクティベーションメールのテンプレートもあります）が、諸々用意するのが大変なので、今回はnodeから直接認証のみを実施してみます（こちらを[参考](https://qiita.com/jp_ibis/items/916e2901d53f34757222)にさせて頂きました）。

**１． Cognitoユーザーを作成**
上記、記事の「２．AWS cognitoでユーザー作成」に沿って作成します。

<img width="621" alt="スクリーンショット 2017-12-16 18.11.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/20512/ccee9089-fb50-9186-d2ca-4c8a62d419b0.png">

<img width="859" alt="スクリーンショット 2017-12-16 18.13.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/20512/e028f815-c94c-b1c2-715f-c67953dae40c.png">


**２． フェデレーテッドアイデンティティを作成する**
- 「フェデレーテッドアイデンティティの管理」-> 「新しいIDプールの作成」
  - ID プール名： 今回は、demo_cognito_authenticated
  - 「認証されていない ID に対してアクセスを有効にする」をチェックを外し、「プールの作成」
  - 「Your Cognito identities require access to your resources」は「許可」



**３． Amazon Cognito Identity SDK for JavaScriptのインストール**
[AWS SDK for JavaScript](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/welcome.html)と合わせて、
[Amazon Cognito Identity SDK for JavaScript](https://github.com/aws/amazon-cognito-identity-js)を使用します。

```
$ npm install amazon-cognito-identity-js --save
```

**４． DynamoDBにアクセスする**
先程のDynamoDBアクセスを*認証あり*で行います。

```js
var aws = require('aws-sdk');
var aws_cognito = require('amazon-cognito-identity-js');

var config = {
    poolId: 'ap-northeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
    region: 'ap-northeast-1'        
};

// ユーザープール設定
var user_pool = new aws_cognito.CognitoUserPool({
    UserPoolId : 'xxxxxxxx', // 1.で作成したユーザプールID
    ClientId : 'XXXXXXXXXXXXXXXXXXXX'　// 1.で作成したアプリクライアント ID
});

// ユーザー決定
const cognito_user = new aws_cognito.CognitoUser({
    Username: 'XXXXX', // 1.でユーザに作成したユーザ名
    Pool: user_pool,
});

// パスワードの設定
const authentication_details = new aws_cognito.AuthenticationDetails({
    Password: 'XXXXXX', // 1.でユーザに設定した仮パスワード
});

// ユーザープール／ユーザー／パスワードを使って認証
cognito_user.authenticateUser(authentication_details, {
    // 成功時
    onSuccess(result){
        aws.config.region = config.region;
        aws.config.credentials = new aws.CognitoIdentityCredentials(
                                        {IdentityPoolId: config.poolId,
                                            Logins: {
                                                'cognito-idp.ap-northeast-1.amazonaws.com/<YOUR_USER_POOL_ID>': result.getIdToken().getJwtToken()
                                            }                                                                                    
                                        });

        // DynamoDBへ接続
        var dynamoDB = new aws.DynamoDB();
        var docClient = new aws.DynamoDB.DocumentClient({service:dynamoDB});

        var params = {
            TableName : 'demo_cognito_table',
            Key: {
                'id': '1'
            }
        };  
        
        docClient.get(params, function(err, data) {
            if (!err){
                console.log(data);
            } else {
                console.log(err);
            }
        });    
    },
    onFailure(err){
        console.error(err);     
    },

    // 初回認証時のパスワード変更を仮パスワードで使いまわす。
    newPasswordRequired(user_attributes, required_attributes){
        cognito_user.completeNewPasswordChallenge(authentication_details.password, user_attributes, this);
    },
});
```

実行すると無認証時と同じく、DynamoDBにアクセスして、データ取得できました。:v_tone1:

```
$ node auth.js 
Cognito Identify Id: ap-northeast-1:xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
{ Item: { id: '1', username: 'katekichi' } }
```

意外と、Cognitoで認証後に取得したトークンと、IAMからCredentialを払い出すために繋ぎ込みが分からず、ハマりましたが、[AWSのドキュメント](http://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-integrating-user-pools-with-identity-pools.html)にちゃんとありましたね・・

# まとめ
ポイントを絞って初心者の方に分かりやすいようにまとめてみましたが、如何でしたでしょうか？
（‥‥というか、半分は自分への覚書ですが:sweat_smile:）

Cognitoは、非常に使用用途が多いサービスですが、慣れないと用語が多く一度に理解するのは厳しいと感じました。色んな方が、書かれている優良な記事を参考にさせて頂きながら、やりたいことを整理して少しづつ小出しに理解していくのが良いように感じました。