動機
APIGatewayとLambdaで、こんなAPIエンドポイントGET /users/{uid}/tasks
を作って、認証済みユーザーのuidが111のときに以下のような制御をしたいものとします。
リクエスト | Allow/Deny | 応答コード |
---|---|---|
GET /users/111/tasks | Allow | 200 |
GET /users/222/tasks | Deny | 403 |
これをCognito使って認可制御したいなと思ったのがきっかけです。
ところでCognitoは3つある
単にCognitoと行っても3つの機能があるので整理しておきます。
本書では以下のように使い分けます。
名前 | 本書での略称 |
---|---|
Federated Identities | Fed ID |
User Pools | User Pools |
Sync | 使わない |
以下にサラッと概要を書いておきます。
Federated Identities
できること
- 一時AWSクレデンシャルを発行する
- 例) ログイン成功したらスマホアプリにIAM Roleを付与してS3のGETできる
- 複数の外部認証プロバイダ(Facebook認証、Twitter認証など)を取りまとめて統一IDを管理する
自分はPublic Client1向けの一時クレデンシャル発行機として扱ってます。
User Pools
できること
- ユーザー管理一式の機能提供
- ID/パスワードでのユーザー認証(Sign in)
- ユーザーの登録(Sign up)
- OpenID Connectに準拠したIdP
- OIDCトークン発行機
この子すごいんですよ。認証つきのアプリならつきもののユーザー管理機能一式がマネージドで使えちゃうんです。
SMSで認証コードを送って確認コードを入れさせるとかも簡単にできちゃいます。
Sync
- ゲームデータなどの保持するのに便利っぽい
- DynamoDB用意するほどでも無いとき用か?
- 本書では扱わない
で、さまよった足跡
トライ1:IAM認証+Cognito Fed ID
これを一番最初に思いつきました。だって、公式ドキュメントにS3へのユーザー別認可制御が簡単そうに書いてあるんですもの。
ポリシードキュメントのResource指定を${cognito-identity.amazonaws.com:sub}
変数使って書けばいいんでしょって。
{
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Effect": "Allow",
"Resource": ["arn:aws:s3:::mybucket/${cognito-identity.amazonaws.com:sub}/*"]
}
これを真似して、
{
"Effect": "Allow",
"Action": [
"execute-api:Invoke"
],
"Resource": [
"arn:aws:execute-api:ap-northeast-1:757XXXXXX:nxcXXXX/*/*/users/${cognito-identity.amazonaws.com:sub}/*"
]
}
こんなのでいいかなって。
ダメだったポイント
一言で言うと:
という文字がパーセントエンコードされるということです。
- CognitoのID形式はこんな感じ
ap-northeast-1:3c5d3ea1-19b2-48a6-9cb2-fa91261cfe7d
- つまりは
GET /users/ap-northeast-1:3c5d3ea1-19b2-48a6-9cb2-fa91261cfe7d/tasks
というリクエストになる
- つまりは
-
:
は%3A
にエンコードされる(RFC3986)
要するに、
ポリシー定義的に認可OKなARN
arn:aws:execute-api:ap-northeast-1:757XXXXXX:nxcXXXX/*/*/users/ap-northeast-1:3c5d3ea1-19b2-48a6-9cb2-fa91261cfe7d/tasks
実際にリクエストされたARN
arn:aws:execute-api:ap-northeast-1:757XXXXXX:nxcXXXX/*/*/users/ap-northeast-1%3A3c5d3ea1-19b2-48a6-9cb2-fa91261cfe7d/tasks
で、:
と%3A
が違うやんけという話で蹴られてしまうんですね。
トライ2:じゃあCustomAuthorizerでやろう
APIGatewayのエンドポイント認可の方式にはもうひとつ、CustomAuthorizerというものがあります。
この機能は認可チェック用のロジックをLambdaファンクションとして実装できるものです。
APIGatewayのマッピングテンプレートで使用可能な変数のリファレンスを見ると$context.identity.cognitoIdentityId
というのがあります。
この値をチェックすればいいんじゃね?と。
ダメだったポイント
一言で言うとCustomAuthorizerではcognitoIdentityId
を知る手段がないです。
CustomAuthorizerに渡されるイベントは以下の形式です。
{
"type":"TOKEN",
"authorizationToken":"<caller-supplied-token>",
"methodArn":"arn:aws:execute-api:<regionId>:<accountId>:<apiId>/<stage>/<method>/<resourcePath>"
}
もらえるとしたらauthorizationToken
なのですが、ここには入ってきません。
正確に言うとcaller側でHTTPヘッダにcognitoIdentityId
をセットしてもらえれば、できます。
が、前提として、
- AWS署名v4使いたい
- リプレイアタック対策とか便利だから
- caller側はAPIGatewayが吐き出すSDKを使いたい
というのがあったのでこの方法は断念しました。
トライ3:じゃあ、Cognito User Pools認証を使う
APIGatewayの認可の方式には更にもうひとつ、Cognito User Poolsがあります。
ダメだったポイント
だめだったというか、やってません。
なぜかというと、認証方式としてTwitterやFB認証をサポートする予定だったのでFed IDの方を使いたかったのです。
じゃあ結局どうしたのか
おとなしく、バックエンド側のLambdaファンクションの中でCognitoIDをチェックするようにしましたとさ。。
-
OAuth,OpenID Connectの言葉。 ネイティブアプリやSPAなどでアクセスキーを安全に保持できないタイプのクライアントを指す。 ↩