こんにちは、抜歯直後で口内環境最悪なエンジニアの最上です。
前編に引き続き、今回はCloudFormationでUserPoolとFederatedIdentityを作ります!
前編を読まれていない方は、前編:シンプルにUserPoolを作るもどうぞ! tech.mti.co.jp
何を作るのか
CloudFormationでこの辺を作る必要があります。リンクはそれぞれの公式ドキュメントです。
- UserPool
- 前編でつくった認証基盤
- UserPoolClient
- UserPoolに登録するAppClient
- IAM Role
- 認証/未認証ユーザーに割り当てるIAM Role
- IdentityPool
- 認証基盤と連携して一時Credentialを発行する認可基盤
- IdentityPoolRoleAttachment
- 認証/未認証ユーザーにどのRoleを割り当てるか
結構量が多くなりますね。IAM Roleは、手で作って割り当てることもできますが、今回はCloudFormationで全部作っちゃうことにしました。
実践
コード
こんな感じになりました。多い!
{ "AWSTemplateFormatVersion": "2010-09-09", "Parameters": { "UserPoolName": { "Description": "UserPool name.", "Type": "String" }, "isUserCreatedByAdminOnly": { "Description": "is user account created by admin only?", "Type": "String", "AllowedValues": ["true", "false"], "Default": "true" }, "IdentityPoolName": { "Description": "IdentityPoolName can contain numbers, letters, underscores and spaces.", "Type": "String", "MaxLength": 128 } }, "Resources": { "UserPool": { "Type": "AWS::Cognito::UserPool", "Properties": { "AdminCreateUserConfig": { "AllowAdminCreateUserOnly": { "Ref": "isUserCreatedByAdminOnly" }, "InviteMessageTemplate": { "EmailMessage": "ID: {username}\nPassword: {####}", "EmailSubject": "Your Temporary Password" } }, "AutoVerifiedAttributes": ["email"], "MfaConfiguration": "OFF", "Policies": { "PasswordPolicy": { "MinimumLength": 8, "RequireLowercase": true, "RequireUppercase": true, "RequireNumbers": true, "RequireSymbols": false } }, "UserPoolName": { "Ref": "UserPoolName" }, "Schema": [{ "Name": "email", "Required": true, "Mutable": true }, { "Name": "name", "Required": true, "Mutable": true }] } }, "UserPoolApp": { "DependsOn": "UserPool", "Type": "AWS::Cognito::UserPoolClient", "Properties": { "ClientName": { "Ref": "UserPoolName" }, "GenerateSecret": false, "RefreshTokenValidity": 30, "UserPoolId": { "Ref": "UserPool" } } }, "IdentityPool": { "DependsOn": "UserPoolApp", "Type": "AWS::Cognito::IdentityPool", "Properties": { "IdentityPoolName": { "Ref": "IdentityPoolName" }, "AllowUnauthenticatedIdentities": false, "CognitoIdentityProviders": [{ "ClientId": { "Ref": "UserPoolApp" }, "ProviderName": { "Fn::Join": ["", ["cognito-idp.", { "Ref": "AWS::Region" }, ".amazonaws.com/", { "Ref": "UserPool" }]] }, "ServerSideTokenCheck": true }] } }, "AuthRole": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Federated": "cognito-identity.amazonaws.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "cognito-identity.amazonaws.com:aud": { "Ref": "IdentityPool" } }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "authenticated" } } }] }, "Policies": [{ "PolicyName": "root", "PolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": [ "mobileanalytics:PutEvents", "cognito-sync:*", "cognito-identity:*" ], "Resource": [ "*" ] }] } }], "RoleName": { "Fn::Join": ["_", ["Cognito", { "Ref": "UserPoolName" }, "Auth", "Role"]] } } }, "UnauthRole": { "DependsOn": "IdentityPool", "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Federated": "cognito-identity.amazonaws.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "cognito-identity.amazonaws.com:aud": { "Ref": "IdentityPool" } }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "unauthenticated" } } }] }, "Policies": [{ "PolicyName": "root", "PolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": [ "mobileanalytics:PutEvents", "cognito-sync:*", "cognito-identity:*" ], "Resource": [ "*" ] }] } }], "RoleName": { "Fn::Join": ["_", ["Cognito", { "Ref": "UserPoolName" }, "Unauth", "Role"]] } } }, "IdentityPoolRole": { "DependsOn": ["AuthRole", "UnauthRole", "IdentityPool"], "Type": "AWS::Cognito::IdentityPoolRoleAttachment", "Properties": { "IdentityPoolId": { "Ref": "IdentityPool" }, "Roles": { "authenticated": { "Fn::GetAtt": ["AuthRole", "Arn"] }, "unauthenticated": { "Fn::GetAtt": ["UnauthRole", "Arn"] } } } } }, "Outputs": { "UserPoolID": { "Description": "UserPoolID", "Value": { "Ref": "UserPool" } }, "ClientID": { "Description": "AppClientID", "Value": { "Ref": "UserPoolApp" } }, "IdentityPoolID": { "Description": "IdentityPoolID", "Value": { "Ref": "IdentityPool" } } } }
Parameters
Parametersには、前編に加えてIdentityPoolの名前を渡せるようにしました。ここで注意したいのは、IdentityPoolNameに使える文字が制限されているということです。本来であれば、ここにAllowedPatternのプロパティを追加して、正規表現でバリデーションするのがよいでしょう(めんどくさくてやってないです、ごめんなさい)。
Resources
Resourcesを1つづつ見ていきます。UserPoolは前編でやったので割愛しますね。
UserPoolApp(UserPoolClient)
DependsOnにUserPoolを設定しています。プロパティとしてUserPoolIdを指定する必要があるため、UserPoolが作成されてから出ないとRefが効かないため、DependsOnは必須となります。今回は、JavaScriptから利用されることを想定して、GenerateSecretはfalseとしています。
IdentityPool
AllowUnauthenticatedIdentitiesをfalseにして、認証済みユーザーと未認証ユーザーのIAMRoleをわけるようにします。認証と連携するのであれば、ここはfalseになるはずです。CognitoIdentityProvidersのProviderNameは難しそうに見えますが、cognito-idp.[region].amazonaws.com/[UserPoolId]
になるようにしているだけです。
外部ID連携をするのであれば、CognitoIdentityProvidersの代わりにSupportedLoginProvidersを設定すればよいようです。しかし、ここの説明がなさすぎて、ぼんやりとしか設定方法がわかりません。Keyの名前どうすんの?とかいろいろ思うところはありますが、これから拡充されていくといいですね。
AuthRole/UnauthRole(IAM Role)
IAM Roleを作成しています。このまま流用するのであれば、Policiesが、各ユーザーに割り当てられるPolicyになるので、そこを修正していただければと思います。
IdentityPoolRole(IdentityPoolRoleAttachment)
この部分のドキュメントのSyntax(JSON)のRolesが、配列になっていますが、ここで思いっきりハマりました。ここはArrayではなくObjectで書けばいいようです。ドキュメントちょっと間違えてますね〜。
Outputs
UserPoolsとFederated Identitiesを利用する際に必要になる3つのIDを出力するようにしました。
ちなみに
IAMを作ることになるので、aws-cliを使ってスタックを作成する場合は、--capabilities CAPABILITY_NAMED_IAM
オプションが必要になります。ご注意を。
おわりに
手でポチポチ作ると、行ったり来たりしてIDを探さないといけなかったです。しかし、CloudFormationを使うと一発で必要なID3つが手に入れられるのが、控えめに言っても最高ですね。
おわり。