:santa:「Amazon Web Services Advent Calendar 2017」20日目の記事です

はじめに

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

先日、「AWS SDK for JavaScript」を使用してCognitoを使用したのですが、諸々の用語や抑えどころが分からずに悩んだ点がいくつかあったので、「はじめてのAWS Cognito」と題してポイントをまとめてみました。

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

1.そもそもCognitoとは?

・ AWSが提供する認証・認可基盤です。通常は、IAMユーザーのアクセスキーや、インスタンスに対するIAMロールを使用して認証します。

2.なぜCognitoを使うのか?

・クライアントアプリ(SPAやスマホのネイティブアプリ)より直接AWSリソースへアクセスしたい要件が増えてきている中で、扱いに困るのがIAMユーザーのアクセスキーシークレットキーの扱いです。

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

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

3.用語について

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

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

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


4.どうやって使うのか

AWS SDK for JavaScriptを使用して、Cognito経由でDynamoDBへアクセスしてみます。

未認証でとりあえず使ってみる

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

1. フェデレーテッドアイデンティティを作成する

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

スクリーンショット 2017-12-07 0.17.09.png


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

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

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

{
    "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」を使用すると、IdentityIdを取得することが出来ます。こちらのIDは、SDKがよろしく保持してくれるので、こちらで管理する必要はありません。

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

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


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仕様は別途ご確認ください。

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

Cognitoユーザによる認証

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

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

1. Cognitoユーザーを作成
上記、記事の「2.AWS cognitoでユーザー作成」に沿って作成します。

スクリーンショット 2017-12-16 18.11.32.png

スクリーンショット 2017-12-16 18.13.20.png

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

3. Amazon Cognito Identity SDK for JavaScriptのインストール
AWS SDK for JavaScriptと合わせて、
Amazon Cognito Identity SDK for JavaScriptを使用します。

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

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

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のドキュメントにちゃんとありましたね・・

まとめ

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

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