CognitoUserPoolsを使うAngular2 SPAのサンプルを動かす

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

丹内です。Public Betaになってしばらく経つCognito UserPoolsですが、そのサンプルアプリを見つけたので動かして観察してみます。

Cognito UserPoolsとは

Amazon Cognitoは、モバイルアプリやSPA(Single Page Application)など信頼できない環境で動作するAWS SDKに対して、クレデンシャルの安全な発行を行うためのサービスです。ユーザ認証とクレデンシャル発行(Cognito Identity)、ユーザデータ管理(Cognito Sync)の機能を有しています。
そんなCognitoに先日追加された機能が、Cognito UserPoolsです。
[新機能] Amazon Cognito に待望のユーザー認証基盤「User Pools」が追加されました!

UserPools以前のCognito(Cognito Federated Identity)では、Authentication providersとしてOIDC IdPやSAMLを利用することができました。
UserPoolsによって、Authentication providersにCognitoが管理する、メールアドレスとパスワードにより認証されるIdentityも利用可能となりました。
大抵のアプリケーションではメールアドレスとパスワードで認証されるユーザ管理機能が付いていると思いますが、UserPoolsによりフルマネージドな機能が提供されます。さらに、クライアントにAWSリソースを操作するクレデンシャルを安全に付加することができるので、クライアントから直接AWSを操作することで、サーバ側の負荷を減らすことも行いやすくなります。

セットアップ

今回動かすサンプルアプリのリポジトリは以下になります。

awslabs/aws-cognito-angular2-quickstart

READMEに沿ってセットアップしていきます。

$ git clone --depth 1 git@github.com:awslabs/aws-cognito-angular2-quickstart.git
$ cd aws-cognito-angular2-quickstart
$ npm install
$ bower install

が、Macの場合はsedのオプションが異なるためAWSリソース作成スクリプトが動作しません。 そこで、シェルスクリプトを以下のdiffのように編集します。

diff --git a/aws/createResources.sh b/aws/createResources.sh
index b96b7ed..aa4f2bb 100755
--- a/aws/createResources.sh
+++ b/aws/createResources.sh
@@ -1,6 +1,11 @@
 #!/usr/bin/env bash

-ROOT_NAME=DevDay
+if [ $# -ne 1 ]; then
+  echo "usage: createResources.sh [name]"
+  exit 1
+fi
+
+ROOT_NAME=$1
 BUCKET_NAME=budilov-$ROOT_NAME
 TABLE_NAME=LoginTrail$ROOT_NAME
 ROLE_NAME_PREFIX=$ROOT_NAME
@@ -44,12 +49,12 @@ aws iam put-role-policy --role-name $ROLE_NAME_PREFIX-authenticated-role --polic

 # Create the user pool
 aws cognito-idp create-user-pool --pool-name $POOL_NAME --policies file://user-pool-policy.json --region $REGION > /tmp/$POOL_NAME-create-user-pool
-userPoolId=$(grep -Po '"Id":.*?[^\\]",' /tmp/$POOL_NAME-create-user-pool | awk -F'"' '{print $4}')
+userPoolId=$(perl -nle 'print $& if m{"Id":.*?[^\\]",}' /tmp/$POOL_NAME-create-user-pool | awk -F'"' '{print $4}')
 echo "Created user pool with an id of " $userPoolId

 # Create the user pool client
 aws cognito-idp create-user-pool-client --user-pool-id $userPoolId --no-generate-secret --client-name webapp --region $REGION > /tmp/$POOL_NAME-create-user-pool-client
-userPoolClientId=$(grep -Po '"ClientId":.*?[^\\]",' /tmp/$POOL_NAME-create-user-pool-client | awk -F'"' '{print $4}')
+userPoolClientId=$(perl -nle 'print $& if m{"ClientId":.*?[^\\]",}' /tmp/$POOL_NAME-create-user-pool-client | awk -F'"' '{print $4}')
 echo "Created user pool client with id of " $userPoolClientId

また、AWSアカウントIDも書き換えておく必要があります。

diff --git a/aws/authrole.json b/aws/authrole.json
index df27aeb..6806522 100644
--- a/aws/authrole.json
+++ b/aws/authrole.json
@@ -23,7 +23,7 @@
         "dynamodb:DeleteItem"
       ],
       "Resource": [
-        "arn:aws:dynamodb:us-east-1:540403165297:table/TABLE_NAME"
+        "arn:aws:dynamodb:us-east-1:012345678901:table/TABLE_NAME"
       ],
       "Condition": {
         "ForAllValues:StringEquals": {

その上で、以下のように実行します。

$ aws/createResources.sh mycognitosample

READMEにも記載されていますが、シェルスクリプト実行時にCognito IdentityへのRoleの設定がエラーになってしまうので、リソース作成完了後に自分で設定する必要があります。

スクリーンショット_2016-07-06_23_29_26

このシェルスクリプトで作成される主要なリソースは、以下のとおりです。us-east-1に作成されます。

  • Cognito UserPool
  • Cognito Federated Identity Pool
  • DynamoDBテーブル(アクセスログ機能用、RCU1/WCU1、Estimated cost $0.59/month)
  • IAMロール(authenticatedとunauthenticatedの2つ)

authenticated roleのポリシーは以下のとおりです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*",
                "cognito-identity:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:BatchGetItem",
                "dynamodb:Query",
                "dynamodb:PutItem",
                "dynamodb:UpdateItem",
                "dynamodb:DeleteItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-east-1:012345678:table/LoginTrailmycognitosample"
            ],
            "Condition": {
                "ForAllValues:StringEquals": {
                    "dynamodb:LeadingKeys": [
                        "${cognito-identity.amazonaws.com:sub}"
                    ]
                }
            }
        }
    ]
}

このIAMロールのTrust Relationshipは以下のとおりです。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "us-east-1:aaaa-bbbb-cccc-dddd"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "authenticated"
        }
      }
    }
  ]
}

動作確認

READMEに沿って、ハードコードされたAWSリソースの値を変更します。

diff --git a/src/app/service/aws.service.ts b/src/app/service/aws.service.ts
index 59b17cb..0cb86b6 100644
--- a/src/app/service/aws.service.ts
+++ b/src/app/service/aws.service.ts
@@ -117,7 +117,7 @@ export class DynamoDBService {

   static getLogEntries(mapArray:Array<Stuff>) {
     var params = {
-      TableName: 'LoginTrail',
+      TableName: 'LoginTrailmycognitosample',
       KeyConditionExpression: "userId = :userId",
       ExpressionAttributeValues: {
         ":userId": AWS.config.credentials.params.IdentityId
@@ -147,7 +147,7 @@ export class DynamoDBService {

   static write(data:string, date:string, type:string):void {
     DynamoDBService.DDB = new AWS.DynamoDB({
-      params: {TableName: 'LoginTrail'}
+      params: {TableName: 'LoginTrailmycognitosample'}
     });

diff --git a/src/app/service/cognito.service.ts b/src/app/service/cognito.service.ts
index a720d23..b2ef4de 100644
--- a/src/app/service/cognito.service.ts
+++ b/src/app/service/cognito.service.ts
@@ -24,9 +24,9 @@ export class CognitoUtil {

   public static _REGION = "us-east-1";

-  public static _IDENTITY_POOL_ID = "us-east-1:fbe0340f-9ffc-4449-a935-bb6a6661fd53";
-  public static _USER_POOL_ID = "us-east-1_PGSbCVZ7S";
-  public static _CLIENT_ID = "hh5ibv67so0qukt55c5ulaltk";
+  public static _IDENTITY_POOL_ID = "us-east-1:aaaa-bbbb-ccccc-dddd";
+  public static _USER_POOL_ID = "us-east-1_AAAABBBBCCCC";
+  public static _CLIENT_ID = "aaaabbbbccccdddd11112222333";

   public static _POOL_DATA = {
     UserPoolId: CognitoUtil._USER_POOL_ID,
@@ -108,7 +108,7 @@ export class CognitoUtil {
 export class UserRegistrationService {

   constructor(@Inject(CognitoUtil) public cognitoConfigs:CognitoUtil) {
-
+    AWSCognito.config.update({accessKeyId: 'anything', secretAccessKey: 'anything'});
   }

   register(user:RegistrationUser, callback:CognitoCallback):void {

READMEの修正だけだと実行時にエラーが出てうまく動かなかったので、AWSCognito.config.update({accessKeyId: 'anything', secretAccessKey: 'anything'});を加えています。

参考:User Pools for Amazon Cognito - CredentialsError: Missing credentials in config

また、ローカルで実行する前に、Cognito UserPoolsの設定を変更しておく必要があります。この設定変更によって、アカウント作成時の確認メールが送信されます。

スクリーンショット_2016-07-06_23_51_46

スクリーンショット_2016-07-06_23_50_58

そして実行します。

$ npm start

> aws-cognito-angular2-quickstart@1.0.0 start /Users/tannai.yuki/.ghq/github.com/awslabs/aws-cognito-angular2-quickstart
> ng server

Livereload server on http://localhost:49152
Serving on http://localhost:4200/

Build successful - 2422ms.

Slowest Trees                                 | Total
----------------------------------------------+---------------------
BroccoliTypeScriptCompiler                    | 1528ms
vendor                                        | 627ms

Slowest Trees (cumulative)                    | Total (avg)
----------------------------------------------+---------------------
BroccoliTypeScriptCompiler (1)                | 1528ms
vendor (1)                                    | 627ms

localhost:4200にアクセスして、アカウントを作成してみます。

スクリーンショット_2016-07-07_0_01_40

アカウント作成直後のUserPoolsの画面が以下です。User StatusがUnauthenticatedになっています。

スクリーンショット_2016-07-07_0_01_55

少し待つと登録メールアドレスに確認コードが送信されてくるので、それをブラウザから入力すると以下のように値が変化します。

スクリーンショット_2016-07-07_0_02_16

改めてログインすると、DynamoDBに履歴が格納され、それをアプリから参照することができます。

スクリーンショット_2016-07-07_0_10_08

スクリーンショット 2016-07-07 0.10.15

この履歴はログイン・ログアウト時にDynamoDBに格納され、それを参照しています。

スクリーンショット_2016-07-07_0_12_39

Federated IdentityのAuthentication Providerに、UserPoolが追加されていることがわかります。

スクリーンショット_2016-07-07_0_22_20

まとめ

サンプルを利用して、Cognito UserPoolsが実際に動く様子を確認することができました。
Angular2のサンプルとしても良いコードだと思うので、みなさんもぜひ動かしてみてください。