LoginSignup
26
15

More than 5 years have passed since last update.

APIGatewayのリソースパスをCognitoで認可制御したい件

Last updated at Posted at 2016-12-09

動機

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つの機能があるので整理しておきます。

AWS Black Belt 資料より

本書では以下のように使い分けます。

名前 本書での略称
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

この子すごいんですよ。認証つきのアプリならつきもののユーザー管理機能一式がマネージドで使えちゃうんです。
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をチェックするようにしましたとさ。。


  1. OAuth,OpenID Connectの言葉。 ネイティブアプリやSPAなどでアクセスキーを安全に保持できないタイプのクライアントを指す。 

26
15
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
15