API Gateway カスタムオーソライザーの挙動を確認してみた

2023.05.08

こんにちは hagiwara です。

API Gateway でカスタムオーソライザーを使用しているのですが、設定方法や挙動の疑問点があったので、実際に動かして確認してみました。
API Gateway REST API で、Lambda オーソライザーを使用した場合を対象としています。

Lambda オーソライザーのインプット

オーソライザーへのインプットは Token と Request の2タイプあります。

Token タイプ

HTTP ヘッダに設定されたトークンの値をインプットとして検証を行います。
Token Sourceに任意のヘッダ名を設定します。Authorization ヘッダを使用するのが一般的だと思います。

インプットイベント

Lambda のインプットイベントは以下のようになりました。
authorizationTokenには、Token Sourceに設定したヘッダの値が格納されます。

{
  "type": "TOKEN",
  "methodArn": "arn:aws:execute-api:[region]:[account-id]:[api-id]/[stage]/[http-method]/[resource-path]",
  "authorizationToken": "token-value"
}

トークンが存在しない場合

トークンに該当するヘッダが存在しないリクエストの場合、オーソライザーは実行されませんでした。
その場合のレスポンスは、ゲートウェイレスポンスのレスポンスタイプUNAUTHORIZEDになりました。
レスポンスの内容を変更するには、このタイプのテンプレートの修正する形になります。
コンテキストのエラーメッセージ$context.error.messageString"Unauthorized"でした。

{
  "message" : "Unauthorized"
}

トークンの検証

Token タイプのオーソライザーにはToken Validationに正規表現を設定することで、トークンを検証できます。
この項目を設定し、正規表現にマッチしない場合、オーソライザーは実行されませんでした。
設定した条件にマッチしなかった場合のレスポンスも、トークンが存在しない場合と同様の挙動となりました。

Request タイプ

HTTPリクエスト全体の値をインプットにして検証を行います。

インプットイベント

Lambda のインプットになるイベントは以下のようになりました。
リクエスト全体の値を取得できています。

{
  "type": "REQUEST",
  "methodArn": "arn:aws:execute-api:[region]:[account-id]:[api-id]/[stage]/[http-method]/[resource-path]",
  "resource": "/[resource-path]",
  "path": "/[resource-path]",
  "httpMethod": "[http-method]",
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate, br",
    "Cache-Control": "no-cache",
    "Content-Type": "application/json",
    "Host": "[api-id].execute-api.[region].amazonaws.com",
    "User-Agent": "PostmanRuntime/7.32.2",
    "X-Amzn-Trace-Id": "Root=1-644cb805-2e9d2d8c4ea4bac444b3729b",
    "X-Forwarded-For": "[source-ip]",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https"
  },
  "multiValueHeaders": {
    "Accept": [
      "*/*"
    ],
    "Accept-Encoding": [
      "gzip, deflate, br"
    ],
    "Cache-Control": [
      "no-cache"
    ],
    "Content-Type": [
      "application/json"
    ],
    "Host": [
      "[api-id].execute-api.[region].amazonaws.com"
    ],
    "User-Agent": [
      "PostmanRuntime/7.32.2"
    ],
    "X-Amzn-Trace-Id": [
      "Root=1-644cb805-2e9d2d8c4ea4bac444b3729b"
    ],
    "X-Forwarded-For": [
      "[source-ip]"
    ],
    "X-Forwarded-Port": [
      "443"
    ],
    "X-Forwarded-Proto": [
      "https"
    ]
  },
  "queryStringParameters": {},
  "multiValueQueryStringParameters": {},
  "pathParameters": {},
  "stageVariables": {},
  "requestContext": {
    "resourceId": "[resource-id]",
    "resourcePath": "/[resource-path]",
    "httpMethod": "[http-method]",
    "extendedRequestId": "EIGw9F6ANjMF-5Q=",
    "requestTime": "29/Apr/2023:06:24:05 +0000",
    "path": "/[stage]/[resource-path]",
    "accountId": "[account-id]",
    "protocol": "HTTP/1.1",
    "stage": "[stage]",
    "domainPrefix": "[api-id]",
    "requestTimeEpoch": 1682749445879,
    "requestId": "28def2c5-288a-45cc-814f-dfbe854fc198",
    "identity": {
      "cognitoIdentityPoolId": null,
      "accountId": null,
      "cognitoIdentityId": null,
      "caller": null,
      "sourceIp": "[source-ip]",
      "principalOrgId": null,
      "accessKey": null,
      "cognitoAuthenticationType": null,
      "cognitoAuthenticationProvider": null,
      "userArn": null,
      "userAgent": "PostmanRuntime/7.32.2",
      "user": null
    },
    "domainName": "[api-id].execute-api.[region].amazonaws.com",
    "apiId": "[api-id]"
  }
}

キャッシュを設定する場合

Request タイプのオーソライザーの検証結果をキャッシュする場合、キャッシュのキーとなるIdentity Sourcesの値を設定する必要があります。
ヘッダやクエリストリングなど、複数のキーを設定することが可能です。

Identity Sourcesが設定されており、リクエストにその値が存在しない場合、オーソライザーは実行されませんでした。この場合も、トークンが存在しない場合と同様、レスポンスタイプUNAUTHORIZEDになりました。

Lambda オーソライザーのアウトプット

オーソライザーのアウトプットは以下の3タイプを Lambda から返却することができます。
その結果、挙動は4パターンとなります。

  • Lambda 正常終了 (ポリシーを返却)
    • ポリシーを評価した結果 Allow だった場合 → 後続のリクエスト処理を実行
    • ポリシーを評価した結果 Allow ではなかった場合 → ACCESS_DENIED
  • Lambda エラー終了 (Unauthorized) → UNAUTHORIZED
  • Lambda エラー終了 (Unauthorized以外) → AUTHORIZER_FAILURE

Lambda 正常終了

Lambda から以下のようなレスポンスを返却します。

{
  "principalId": "[user-id]",
  "policyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": "execute-api:Invoke",
        "Effect": "Allow|Deny",
        "Resource": "arn:aws:execute-api:[region]:[account-id]:[api-id]/[stage]/[http-method]/[resource-path]"
      }
    ]
  },
  "context": {
    "[key]": "[value]",
  },
  "usageIdentifierKey": "[api-key]"
}

policyDocument.Statement の内容を評価した結果、対象のリソースが許可されていた場合、後続のリクエスト処理が実行されます。

許可されていなかった場合は、レスポンスタイプACCESS_DENIEDになりました。
コンテキストのエラーメッセージ$context.error.messageStringは以下でした。

"User is not authorized to access this resource with an explicit deny"

オーソライザーのキャッシュを使用していない場合は、受け取ったmethodArnに対する、Allow または Deny を返す形で問題ないと思います。
キャッシュを使う場合は、キャッシュのキー (Token Source, Identity Sources) に対して、オーソライザーが設定されているすべてのリソースを網羅したステートメントのリストを返す必要があります。
詳細はいわささんのブログを参照してください。

Statement を空配列にしてみたところ、レスポンスタイプAUTHORIZER_CONFIGURATION_ERRORになりました。Allow を設定しない場合は Deny を設定しておく必要がありそうです。

policyDocument以外の項目は、ない場合もエラーにはなりませんでした。

principalIdおよびcontextに設定した値は、後続のリクエスト処理および、レスポンスタイプACCESS_DENIEDのテンプレートから参照可能です。

Lambda エラー終了 (Unauthorized)

Lambda が "Unauthorized" のエラーを返却した場合、レスポンスタイプUNAUTHORIZEDのレスポンスとなります。
エラーメッセージ$context.error.messageString"Unauthorized"となっていました。

Nodeの実装例ではcallback("Unauthorized")となっています。
一部しか試せてませんが、他のランタイムの場合もエラーの文字列表現を"Unauthorized"にすることで同様の結果になりました。

"Unauthorized"は大文字小文字の区別をしているようで、"unauthorized""UNAUTHORIZED"の場合は判定されませんでした。

Lambda エラー終了 (Unauthorized 以外)

Lambda が"Unauthorized"以外のエラーを返却した場合、レスポンスタイプAUTHORIZER_FAILUREになりました。 エラーメッセージ$context.error.messageStringnullになっていました。

その他

インプットアウトプット以外に試してみた挙動を記載します。

Lambda エラー終了はキャッシュされるか

Lambda オーソライザーがエラーを返した場合、その結果はキャッシュされませんでした。
インプット (Tokenタイプ, Requestタイプ)、アウトプット(Unauthorized, その他のエラー)、の組み合わせを試してみましたが、すべての場合でキャッシュはされず、リクエストの度にオーソライザーが実行されました。

Lambda オーソライザーキャッシュの消し方

CLI から flush-stage-authorizers-cache を実行することで、キャッシュをクリアすることができました。

aws apigateway flush-stage-authorizers-cache --rest-api-id [api-id] --stage-name [stage]

API Key が不正な場合オーソライザーは実行されるか

実行されました。
API Gateway の機能である API キーを必須とし、対象のx-api-keyヘッダが不正な場合や存在しない場合を試してみましたが、API キーの検証の前にオーソライザーは実行されるようです。

おわりに

以上、Lambda オーソライザーの動きを確認してみました。
ドキュメントからは読み取りづらい場合もありますが、簡単に試してみることができるので、迷ったら簡単なサンプルを作って動かしてみるのが早くて確実かもしれません。