Amazon API Gateway で x-api-key ヘッダーを使わずに API キーを使ってみた

2023.02.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

いわさです。

API Gateway はリクエストの x-api-key ヘッダーでの API キー設定をサポートしています。

先日の記事では API キーと使用量プランを使って API キーを要求する API で、さらにどの API キーでアクセス出来るのかを制限するアプローチをカスタムドメインで試してみました。

この際に API キーをリクエストの x-api-key ヘッダーに設定しましたが、API を利用する一部クライアントの都合で x-api-key ヘッダー以外のヘッダーを使いたかったり、あるいは URL パラメータなどでのキー指定を行いたい場合があります。

今回は URL パラメータに API キーを含めてリクエストし、それを使う方法を試してみます。

なお、この記事では API キーのような情報を URL パラメータに含めるべきではないとか、そういったところには言及しません。

オーソライザーでカスタマイズが出来る

API キーは設定方法に 2 つの選択肢が用意されています。

1 つは x-api-key ヘッダーで指定する方法です。
もう 1 つはオーソライザーを使う方法です。

オーソライザーでは何かしらの認可処理を行いどの API へのアクセスを許可・拒否するのかをレスポンスで表現することで、API Gateway のバックエンドへのアクセス許可を行うことが出来ます。

このレスポンスにオプションとして、どの使用量を使うかの API キーを含めることが出来ます

{
  "principalId": "user",
  "policyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": "execute-api:Invoke",
        "Effect": "Deny",
        "Resource": "arn:aws:execute-api:us-west-2:123456789012:ymy8tbxw7b/dev/GET/"
      }
    ]
  },
  "usageIdentifierKey": "{api-key}"
}

Lambda オーソライザー用の関数

Lambda オーソライザー用の関数はブループリントから作成することが出来るのでこちらをベースに作り込むと楽です。
余談ですが関数のブループリント選択画面変わったような気がする。

Lambda オーソライザーで URL パラメータを取得し、それをレスポンスの API キーに設定するような処理を想定します。
特定ヘッダーからの取得ではないのでリクエストベースのオーソライザーを想定し、上記ブループリントに以下のハイライト部分の追加・変更を行いました。

lambda_function.py

import re

def lambda_handler(event, context):
    # print("Client token: " + event['authorizationToken'])
    print("Method ARN: " + event['methodArn'])

    principalId = 'user|a1b2c3d4'

    tmp = event['methodArn'].split(':')
    apiGatewayArnTmp = tmp[5].split('/')
    awsAccountId = tmp[4]

    policy = AuthPolicy(principalId, awsAccountId)
    policy.restApiId = apiGatewayArnTmp[0]
    policy.region = tmp[3]
    policy.stage = apiGatewayArnTmp[1]
    # policy.denyAllMethods()
    policy.allowAllMethods()
    authResponse = policy.build()
   
    authResponse['usageIdentifierKey'] = event['queryStringParameters']['hogeapikey']

    return authResponse

今回の検証では API キーだけに注目しているので何か認可は行わないです。
そのため authorizationToken の取得はせずに、API も全て許可して API キーだけに任せてます。
あと printicalId は特に今回の API では使わないので適当です。

リクエストベースオーソライザーの作成

ではカスタムオーソライザーを作成しましょう。

Type は Lambda で、イベントペイロードはリクエストです。

トークンベースとリクエストベースのオーソライザーのペイロードの違いは以前検証したことがあるのでこちらをご覧ください。
今回はキャッシュを使ってないですが、API キーだけで識別するのであればオーソライザーの Lambda 実行回数を抑えるためにオーソライザーキャッシュ効かせるのもアリかもしれない。

先程の関数を指定してテストしてみましょう。

オーソライザーのレスポンスの一部が取得出来ているので良さそうです。

API キーのソースでオーソライザーを設定する

関数の認可で作成したカスタムオーソライザーを設定します。
また、API キーの必要性はtrueを設定してください。

クエリ文字列を必須にしておくと、オーソライザー関数実行前の前提条件のチェックとして利用出来るので必須設定にしておいても良いかもしれませんが、このあたりは任意です。

参考までに、この時点で私が API キーを URL パラメータで引き渡しても 403 Forbidden となり、API Gateway のログには以下の内容が記録されていました。
API キーが正しくない、あるいはセットされていないと。

(de73b353-4630-4f3d-be80-25f06f189a63) API Key not authorized because method 'GET /hoge1' requires API Key and API Key is not associated with a Usage Plan for API Stage rjw5cmkpf0/hoge0205stage: API Key was required but not present

ただし、ログ上で確認すると API キーは設定されていそうです。

(de73b353-4630-4f3d-be80-25f06f189a63) Authorizer result body before parsing: {
    "principalId": "user|a1b2c3d4",
    "policyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "execute-api:Invoke",
                "Effect": "Allow",
                "Resource": [
                    "arn:aws:execute-api:ap-northeast-1:123456789012:rjw5cmkpf0/hoge0205stage/*/*"
                ]
            }
        ]
    },
    "usageIdentifierKey": "hoge0205key1aaaaaaaaaa"
}

うっかりしていたのですが、API ごとに API キーのソース設定というものがあることを失念していました。
今回の方法でオーソライザーで設定した API キーを使う場合はこちらを変更する必要があります。

ヘッダーとオーソライザーの選択肢があるのでここでオーソライザーを選択し、API を再度デプロイしました。

完成

では URL パラメータを指定してリクエストしてみます。

% curl -i "https://rjw5cmkpf0.execute-api.ap-northeast-1.amazonaws.com/hoge0205stage/hoge1?hogeapikey=hoge0205key1aaaaaaaaaa"
HTTP/2 200 
date: Sun, 05 Feb 2023 04:10:51 GMT
content-type: application/json
content-length: 20
x-amzn-requestid: 52ceeefa-52b1-4998-9771-f0dff2379217
x-amz-apigw-id: f2PbwHW0NjMFY9w=
x-amzn-trace-id: Root=1-63df2c4b-5916e766594a810415a18f3a;Sampled=0

"Hello from Lambda!"

おお、うまくいってますね!

次に x-api-key ヘッダーで指定してみます。

% curl -i -H "x-api-key:hoge0205key1aaaaaaaaaa" "https://rjw5cmkpf0.execute-api.ap-northeast-1.amazonaws.com/hoge0205stage/hoge1"                                  
HTTP/2 401 
date: Sun, 05 Feb 2023 04:11:03 GMT
content-type: application/json
content-length: 26
x-amzn-requestid: 6c38b2cf-3071-4754-a53b-e34572a2ec43
x-amzn-errortype: UnauthorizedException
x-amz-apigw-id: f2PdqGK-tjMFyZQ=

{"message":"Unauthorized"}

こちらは使えなくなりました。
オーソライザーでの指定が必ず必要になりました。

さいごに

本日は Amazon API Gateway で x-api-key ヘッダーを使わずに API キーを使ってみました。

API キーにオーソライザーを使う方法はすごく良いですね。様々なユースケースに対応することが出来そうです。

簡単なところだと今回のようなパターンとか、あるいは x-api-key 以外のヘッダーに設定出来るようにするとか暗号化した API キーを使えるようにするとか色々なカスタマイズが出来そうです。

また、SaaS on AWS ではマルチテナントで使用量プラン使って Tier ごとにスロットリング設定できるみたいな話が出るのですが、認証認可部分は通常のトークンベースで行いつつ、利用者のリクエストに含めない形でオーソライザーで API キーを設定出来れば使用量プランの各機能を使うことも出来そうですね。