はじめに
本稿は以下の二部構成となります。
【Amplify】APIのAuthorization方式「AWS_IAM」を理解する#1 ~解説編~【AWS】 ←いまココ
【Amplify】APIのAuthorization方式「AWS_IAM」を理解する#2 ~実践編~【AWS】 ※作成中
昨今、新たなサービスの提供の際には、SPAやモバイルアプリで実現することが当たり前の状況になってきているかと思います。
AWSであればAmplifyというフレームワークを使うことで、基本的な構成をすぐに作ることができますが、そのベースとなる仕組みについての解説が少なく、少しでも想定構成を外れたものを作ろうとすると、途端に苦労します。
Amplifyで利用可能な(というより、AppSync・API Gatewayで利用可能な)Authorizationには以下の4つの方式がありますが、主に利用する方式は AWS_IAM か AMAZON_COGNITO_USER_POOLS のいずれかになると思います。
- API_KEY
- AWS_IAM
- AMAZON_COGNITO_USER_POOLS
- OPENID_CONNECT
本稿ではこのうち「AWS_IAM」の仕組みについて説明します。
AWS Amplifyにて生成される環境と同等のものを手動で作成し、確認していくこととします。
Prerequisite / 前提となる知識
本稿は、下記の前提知識があることを想定しています。
- AWS IAMの基本的な仕組みを理解している
- AWS API発行時、どのようにしてAccessKeyId/SecretAccessKeyを利用しているのか(Signature V4)
- AWS Security Credentialsには、long-termのものとshort-termのものがある
- IAM RoleとAssumeRoleの仕組みを理解している
- API Gatewayの基本的な仕組みを知っている
- ResourceやMethodへの設定内容・変更内容を反映させるには、Deployが必要
- AWS Management ConsoleからTestを実行できる。
- リクエストの定義は、Method RequestとIntegration Requestの2段階で設定する。
- Identity-Based policyとResource-Based policyの意味を理解している
- https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_identity-vs-resource.html
- Identity-Based policyはいわゆるIAM permissions。操作する主体(Principal/Entity)に付与される。
- Resource-Based policyは、操作される客体に付与されるpolicy。以下がその代表例。
- S3 Bucket Policy
- SQS Policy
- KMS Key Policy
- Lambda Permissions
以下、いくつか補足します。
AWS APIとSignature V4
アプリケーションプログラムから各AWSサービスを利用する場合、大体においてAWS SDKやその他フレームワーク/ライブラリを利用するものと思います。AWSサービスの仕組みを紐解いていくと、それらの機能は最終的にはRESTFulなAPI群(本稿ではAWS APIと呼びます)で提供されていることが分かります。
それらAWS APIにおいて、APIリクエスト発行者が正統なユーザであるかどうか(Authentication)をどのように確認し、必要な権限があるかどうか(Authorization)をどのようにチェックしているのか、まず理解する必要があります。
【ASW】AWS APIの認証・認可の仕組みを理解する【Signature V4】 - Qiita
IAM RoleとAssumeRole
Cognito Identity Poolをはじめとし、各AWSサービスが連携する際にはAssumeRoleという仕組みが活用されています。
これは簡単に言うと、「あるIAM Entity(≒IAM Userなど)が、別のIAM Roleの権限を得ることを要求し、その権限を一時的に得ること」といえます。
AssumeRoleする主体は、IAM UserだけでなくAWSサービスも対象となりえます。本稿の範囲においては、Cognito Identity Poolがサービスが、あるIAM RoleをAssumeRoleしてAWS Temporary Security Credentialsを得る、という点が重要になってきます。
AssumeRole - AWS Security Token Service
https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
assume-role — AWS CLI 1.18.21 Command Reference
https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role.html
環境/構成
想定構成
まず典型的なSPA/モバイルアプリの構成を考えます。概ね下記のようなるかと思います。
※通常であればSPA用の静的コンテンツ配信用のCloudFront及びS3 Bucketが存在しますが、本稿の趣旨とは関係がないため省略しています。
本稿の検証で作成するAWSリソース名はそれぞれ以下とします。
- Cognito User Pool
- Name:
- practice_userpool
- Id:
- us-west-2_sq*******
- App client:
- Name:
- practice_userpool_mobile_client
- App client id:
- 18rd**********************
- Name:
- Name:
- Cognito Identity Pool
- Name:
- practice_idpool
- Id:
- us-west-2:785e****----********
- Unauthenticated role:
- Cognito_practice_idpoolUnauth_Role
- Authenticated role:
- Cognito_practice_idpoolAuth_Role
- Name:
- API Gateway
- Name:
- practice-api
- Id
- al********
- Name:
- AWS Lambda
- Name:
- echo-function
- Name:
- DynamoDB
- ※今回は利用しない
APIにおけるAuthorization
まず、APIのAuthorization設定により制御する箇所を確認します。下図の通り、API Gatewayの入り口、若しくは各AWSリソースの入り口で制御することとなります。
API Gateway以降のAuthorization
API Gateway/各AWSリソースの入り口だけではなく、その後もいくつかAuthentication/Authorizationが行われる個所がありますので、併せてみていきます。
具体的には以下の2点でAuthorizationが行われます。
- API GatewayからLambda関数の実行
- Lambda関数からほかのAWS APIの実行
AWS_IAM方式の解説
概要
API GatewayのAuthorization方式「AWS_IAM」を簡単に説明すると、AWS APIだけではなくAWS利用者が提供するAPI(つまりAPI Gateway経由で提供するAPI)も、Signature V4を利用してAuthentication/Authorizationする、というものになります。
ということは、クライアントアプリに何らかのIAM Entity(IAM User/Role)の権限を付与する必要がある、つまりAPIリクエストを実行するにあたって、AWS Security Credentialsが必要となる、ということになります。
ではクライアントアプリはどのようにしてAWS Security Credentialsを入手するのでしょうか?この役割を果たすのが「Amazon Cognito Identity Pool」(旧称:Federated Identities)です。
下記にある通り、これは一時なAWS Security Credentialsを得ることができるサービスとなります。
Amazon Cognito Identity Pools (Federated Identities) - Amazon Cognito
https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-identity.html
Amazon Cognito identity pools (federated identities) enable you to create unique identities for your users and federate them with identity providers. With an identity pool, you can obtain temporary, limited-privilege AWS credentials to access other AWS services. Amazon Cognito identity pools support the following identity providers:
※以降省略
この方式でAPI GatewayのAuthorizationを実現する場合、以下の4つの設定をおこなう必要があります。
- Cognito Identity Poolに、認証を連携するための外部のIdentity Provider(IdP)を紐づける
- Cognito Identity PoolにIAM Roleを紐づける
- 紐づけたIAM RoleのPermissionsには、API GatewayのAPIを実行するための権限を付与する
- API Gateway上の必要なResource・Methodにて、Authorizationを「AWS_IAM」に変更する
まず重要なポイントとして、Cognito Identity Pool自体は認証機能は提供しません。飽くまで外部のIdentity Providerと連携して、認証が成功したらAWS Temporary Security Credentialsを生成する、というサービスになります。
認証機能を提供するの外部IdPの一つがCognito User Poolである、と言えます。
利用できるIdPは大きく2種類に分かれます。OpenID ConnectやSAML2.0を提供する外部IdPと、独自に実装した認証機能(Developer Authenticated Identities)を用いることができます。
次に、AWS Security Credentialsを発行するということは、何らかIAM Permissionsが紐づいている必要があります。Cognito Identity Pool作成時、認証されたユーザ/認証されないユーザに対して紐づけるIAM Roleを指定する必要がありますが、ここで指定したIAM Roleがこれに該当します。
クライアントアプリがAWS Temporary Security Credentialsを得ることができれば、あとはSignature V4に従ってAPIリクエストに署名を付与することで、Authorization方式が「AWS_IAM」に設定されているAPIを呼び出すことが可能となります。
以下から、各AWSリソースの設定がAWS Management Consoleのどの設定に対応しているのかを簡単に確認していきます。
1. Cognito Identity Poolに、認証を連携するための外部のIdentity Provider(IdP)を紐づける
Identity Poolの設定にAuthentication Providersという箇所がありますが、ここで外部のIdPを設定することとなります。
当該IdPから見た場合、Cognito Identity Poolは一つのアプリケーションとなります。当たり前ですが、外部のIdP側にも設定が必要となります。
2. Cognito Identity PoolにIAM Roleを紐づける
Identity Pool作成時、Unauthenticated roleとAuthenticated roleの設定が必要になります。上記で設定したIdPとの認証が失敗した場合(若しくは認証を実施しない場合)はUnauthenticated roleの、IdPとの認証が成功した場合は、Authenticated roleの権限が付与されます。つまり、クライアントアプリはとIdPとの認証結果により、付与されるAWS Temporary Security Credentialsの権限が変わる、ということが言えます。
今回の検証においては、AWS Management ConsoleからGUIを利用してIdentity Poolを作成しています。以下の2つのIAM Roleが作成され、Identity Poolに付与されています。
- Cognito_practice_idpoolUnauth_Role
- Cognito_practice_idpoolAuth_Role
3. 紐づけたIAM RoleのPermissionsには、API GatewayのAPIを実行するための権限を付与する
上記で生成されたAuthenticated roleには、以下ようなPermissionsの付与が必要となります。これは、当該IAM Roleの権限を得たIAM Entityに対し、Resourceで指定したAPI(API Gatewayで定義されるAPI)の実行を可能とするものとなります。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"execute-api:Invoke"
],
"Resource": [
"arn:aws:execute-api:us-west-2:************:al********/*"
],
"Effect": "Allow"
}
]
}
API GatewayのAPIのIdは、AWS Management Console上、下記から確認できます。
4. API Gateway上の必要なResource・Methodにて、Authorizationを「AWS_IAM」に変更する
Cognito Identity Pool及びAWS Temporary Security Credentialsを利用した方式で、APIのAuthorizationを行いたい場合、API Gatewayの対象のResourceのMethodから、「Method Request」を選択し、Authorizationを「AWS_IAM」に変更します。
Cognito Identity Poolを少し解説する
詳細を説明すると長くなるので割愛しますが、Cognito Identity Poolのポイントは以下の通りです。
- 外部のIdentity Provider(以下、IdP)と認証を連携する。外部IdPでの認証に成功したユーザに対しAWS Temporary Security Credentialsを発行する
- 認証が成功したユーザに対し、Cognito Identity Pool内部でUniqueなID(IdentityId)を発行する
- 外部のIdPとして、FacebookやGoogleなどをはじめとし、SAMLのIdP、独自認証APIなど様々な認証方式をサポートしている。Cognito Identity Poolから見た場合、Cognito User PoolはそれらIdPの一つとして位置づけられる。
- 外部のIdPでの認証が成功した場合、IdPから当該ユーザ対してIdToken/Assertion(OpenID ConnectやSAML2.0で認証された場合)が発行される。これはAssumeRoleWithWebIdentity API、若しくはGetCredentialsForIdentity APIの引数として渡す。
ちなみに、Cognito User PoolはOAuth2.0/OpenId Connect準拠(独自機能/拡張は非常に多くありますが)ですが、Cognito Identity Poolは完全にAWS独自の仕組みです。
認証のフローについては下記スライドが分かりやすいです。OpenID ConnectやSAML2.0を提供する外部IdPとの認証、独自に実装した認証機能とで大きくフローが分かれます。
AWS Black Belt Tech シリーズ 2015 - Amazon Cognito
https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-tech-2015-amazon-cognito/13
https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-tech-2015-amazon-cognito/16
Cognito Identity Poolでは、以下の4種類の認証フローがあります。前者2つが外部IdPを利用するパターン、後者2つが独自認証を利用するパターンとなります。それぞれが、上記スライドとも対応しています。
- External Provider Authflow
- Enhanced (Simplified) Authflow
- Basic (Classic) Authflow
- Developer Authenticated Identities Authflow
- Enhanced Authflow
- Basic Authflow
認証フローの詳細については下記公式ドキュメントが参考になります。
幾つか認証フローのパターンがありますが、最終的にはSTS(Security Token Service)にて、AWS Temporary Security Credentialsを発行していることが分かると思います。
Identity Pools (Federated Identities) Authentication Flow - Amazon Cognito
https://docs.aws.amazon.com/cognito/latest/developerguide/authentication-flow.html
ちなみに余談ですが、Cognito Identity Poolの方がCognito User Poolより古いです。またCognito User Poolが存在しなかった時代、Cognito Identity Poolは、単に「Amazon Cognito」と呼ばれていました。
実際、公式ドキュメントの履歴を見てみると、Cognito Identity Poolのリリースが2014年、Cognito User Poolのリリースが2016年であることが分かります。
古い公式ドキュメントを参照する場合は、名称の変遷に気を付ける必要があります。
Amazon Cognito のドキュメント履歴 - Amazon Cognito
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-document-history.html
変更 説明 日付 Cognito ユーザープールの一般提供 Cognito ユーザープール機能を追加しました。この機能を使用して、ユーザーディレクトリを作成、管理し、ユーザープールを使用してモバイルアプリケーションまたはウェブアプリケーションにサインアップとサインインを追加します。詳細については、「Amazon Cognito ユーザープール」を参照してください。 2016 年 7 月 28 日 Amazon Cognito の一般提供 2014 年 7 月 10 日
API GatewayからAWS Lambda関数を実行するための許可
上記までのAuthorizationとはまた別に、AWS Lambda関数には、誰がその関数を呼び出せるかを制御する仕組みがあります。
これは当該Lambda関数自体に設定するPolicyとなります。下図の個所で制御するイメージです。
API GatewayからLambda関数を実行するためには、当該Lambda関数側に、どのAPI Gatewayからの実行を許可するのか、という設定を付与する必要があります。
Using Resource-Based Policies for AWS Lambda - AWS Lambda
https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html
AWS内での権限制御において、その権限内容はPolicyドキュメントとして表現されますが、何らかAWS APIを実行する主体に付与するIAM Policyと、実行される客体に対するPolicyの2種類に分かれます。前者をIdentity-based policy、後者をResource-based policyと呼ぶことがあります。
後者のものとして、このLambdaのResource-based policyのほか、S3 BucketのBucket Policyがこれに該当します。
AWS Lambda関数からほかのAWSにアクセスするための許可
これは本稿の目的とする、API GatewayのAuthorization方式とは直接は関係ないのですが、併せて説明します。
AWS Lambda関数にはIAM Roleを付与する必要がありますが、これは当該Lambda関数から他のAWSサービスのAPIを実行する際に必要となります。下図の個所で制御されます。
実行に際してLambda関数は、付与されたIAM Roleに基づいてAWS Temporary Security Credentialsを得ます。これを利用してSignature V4の署名を生成し、この署名を付与して他のAWSサービスのAPIを実行します。
従って、当該Lambda関数の実行においては、Cognito Identity Poolからクライアントアプリに向けて発行されたAWS Temporary Security Credentialsの権限(=Identity PoolのAuthenticated role)は影響しません。
認証からAPI実行完了までの流れを整理する
全ての設定が完了した状態で、クライアントアプリが外部IdPと認証してから、最終的にAWS Temporary Security Credentialsを得てAPIを実行し、レスポンスを得るまでの流れを整理します。クライアントアプリ⇔Cognito Identity Pool間のインタラクションについては、External Provider AuthflowのEnhanced (Simplified) Authflowを利用するものとします。
- 外部IdPとの認証とIdTokenの取得
- クライアントアプリは、Cognito Identity PoolのAuthentication Providersとして設定されたIdPと認証する。本稿のケースであれば、「practice_userpool」という名称のCognito User Pool。
- クライアントアプリは、外部IdPとの認証方式がOpenID Connect準拠であればIdTokenを、SAML2.0であればAssertionを取得する。本稿ではCognito User Poolを利用するので、ここではIdTokenを得る。
- IdTokenにはそのIdP内で必ずUniqueとなる値(sub)が含まれており、当該IdPの範囲内でユーザを特定できるようになっている。
- また、IdTokenはIdPによる署名が付与されており、改竄ができないようになっている(というより改竄されたら検出できる)ため、Cognito Identity Poolはその内容を信頼することができる。
- 得られたIdTokenをもとに、AWS Temporary Security Credentialsを取得する
- クライアントアプリは、External Provider AuthflowのEnhanced (Simplified) Authflowを開始する。
- まず、Cognito Identity Pool APIの GetId API を実行する。このAPIのLogins引数内に、上記で得られたIdTokenを指定する。
- クライアントアプリは、GetId API の実行の結果、Identity Idを取得することができる。
- 次にクライアントアプリは、API_GetCredentialsForIdentity APIを実行する。上記項番3で得られたIdentity Idと、項番1で指定したLoginsと同等の値を引数として指定する。
- API_GetCredentialsForIdentity APIが成功すると、クライアントアプリはAWS Temporary Security Credentialsを得ることができる。これは、Authenticated Roleに紐づけられたPermissionsの権限を持つ。
- 得られたAWS Temporary Security Credentialsをもとに、API GatewayのAPIを実行する
- クライアントアプリは、APIリクエストを作成する
- 作成したAPIリクエスト内容に対し、Signature V4で署名を作成する。
- 作成した署名等をリクエスト内容に付与し、API GatewayのAPIを実行する
- API Gatewayにてクライアントアプリからの署名をチェックする
- リクエスト内容に付与された署名をもとに、当該APIリクエストが正統なユーザからのものであるか、必要な権限が足りているか(つまり、Cognito Identity Poolに付与されたAuthenticated Roleに、当該API Gatewayの実行権限があるか)をチェックする。
- API GatewayからLambda関数を実行する
- API Gatewayサービスが、Lambda関数 echo-function を実行する
- Lambda関数は、自身のResource based policyを確認し、当該API Gatewayインスタンスからの実行が許可されているかチェックする
- チェックの結果、必要な権限が付与されていれば、ハンドラ内に定義された内容を実行する
- Lambda関数からほかのAWSサービスにアクセスする
- Lambda関数は、実装内容に応じて他のAWSリソースのAPIを実行する。これは、Lambda関数に付与されたIAM Role/Permissionの権限で実行される。
- 適切な権限があれば当該AWS APIの実行は成功し、Lambda関数に結果が返却される
- Lambda関数はその後処理を継続し、レスポンスを返却する
- API GatewayはLambda関数の実行結果を受け取る
- API Gatewayは、Lambda関数の実行結果をもとに、クライアントアプリにレスポンスを返却する
認証からAPI実行完了までの流れを図にする
上記の流れを図にすると、それぞれ下記のようになります。
1. 外部IdPとの認証とIdTokenの取得
2.得られたIdTokenをもとに、AWS Temporary Security Credentialsを取得する
3.得られたAWS Temporary Security Credentialsをもとに、API GatewayのAPIを実行する
4.API Gatewayにてクライアントアプリからの署名をチェックする
5.API GatewayからLambda関数を実行する
6. Lambda関数からほかのAWSサービスにアクセスする
7.API GatewayはLambda関数の実行結果を受け取る
8.API Gatewayは、Lambda関数の実行結果をもとに、クライアントアプリにレスポンスを返却する
Loginsプロパティの指定について
GetId API や GetCredentialsForIdentity API の引数として「Logins」というプロパティがありますが、APIリファレンス上、これに関する説明が些か不足しているように思います。
https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetId.html#API_GetId_ResponseSyntax
https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html
下記の配下のページに、FacebookやAmazonを利用する場合のLoginsプロパティの実装例が書いてありますが、Cognito User Pool用の例が無かったりします。
Identity Pools (Federated Identities) External Identity Providers - Amazon Cognito
https://docs.aws.amazon.com/cognito/latest/developerguide/external-identity-providers.html
Open ID Connect Providers (Identity Pools) - Amazon Cognito
https://docs.aws.amazon.com/cognito/latest/developerguide/open-id.html
Cognito User Poolを利用する場合は、おそらく下記のような指定になります。(※別途確認する)
{
"cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID>": "<IdToken>"
}
{
"cognito-idp.us-west-2.amazonaws.com/us-west-2_sq*******": "eyJ........"
}
その他補足
実はCognito Identity Poolの大きな役割としてもう一つ、外部のIdPのユーザアカウントを一つのIdentityIdに紐づけることができる(≒名寄せができる)というものがあります。
上述のLoginsプロパティには複数の値を指定することができ、Facebookでの認証結果やAmazonでの認証結果など、複数の認証結果を含めることができます。
それら外部IdPの認証結果を一つのIdentityIdに紐づけることで、どのSocial Providerでログインしても、AWS内では同一のユーザとして扱う、ということが実現できます。
おわりに
AWS Amplifyはコマンド一つでこの構成を作成することができ、非常に便利に使えるようになっています。
しかしながら、中身をブラックボックスのまま使っていった場合、トラブル発生時に対応できなかったり、CloudFormationやCDKなどのIaCを併用する場合に管理しきれなくなったり、困ることとなります。
最初はブラックボックスのまま利用するというアプローチも良いと思いますが、どこかで必ずその仕組みを理解しなければならないタイミングが来ますので、ここに解説をまとめておきます。