blog?

API Keys and Usage Plans with AWS API Gateway

13 December 2016

This is an update of the example code from the post Going Serverless with AWS Lambda and API Gateway. Grab that code here.

AWS makes it easy to throw up a quick API. But how do you prevent abuse? You are, after all, paying for the resources that others are using. One way is with API keys and usage plans.

What are API keys?

In the general sense, API keys are a token that authorizes you to access a resource. API keys are not a form of authentication, as API keys are not generally considered secrets nor are they necessarily protected as such.

On AWS, API Keys are a component of the API Gateway product offering. They are a system that allows for the creation and management of API keys as associated with API Gateway specific concepts, such as deployments or Usage Plans, or AWS specific concepts, such as the SaaS Marketplace.

What are Usage Plans?

Usage Plans are an API Gateway component that allows you to assign specific API keys to your API consumers and control the rate and quantity of requests that they make. Additionally, you can also query how many requests were made to your API by each Usage Plan. If you bill per-request for your API, this feature allows you to easily track how many requests were made, report on it, and through integration with other Amazon services, easily bill for them.

Usage plans aren't strictly necessary for you to use API keys. However, I would recommend that if you are exposing APIs to clients, setting up rate limiting and request limits as well as tracking these metrics are necessary to ensure your services are not abused. Sometimes, bugs in the software of clients can lead to runaway request conditions. Neither you (since your API is limited by AWS) nor your client (since they are being billed by request or would like to at least access your service) would like situations like this. Limit your APIs.

Why use API Keys?

The primary strength of the API key approach is simplicity. The value is passed as part of the x-api-key header. There is no tricky crypto or request/response challenge sequence to contend with. Consumers of your API will be able to quickly and easily integrate with you. In a situation where you bill for usage, this can help with onboard and reduce time to revenue generation.

What about arguments against?

API keys doesn't integrate with Amazon's IAM offering. This can introduce challenges when using API Gateway to manage solely internal services when you want to central manage all access with IAM. You can still manage access with IAM, just not with API keys. For that, you will need to use a custom authorizer (a perfectly fine, albeit more involved, solution).

Too, API keys don't support HMAC signatures or other crypto goodies. The key is a string. Having the string gives you access. Depending on your use case, you may want to opt for a more secure approach.

Alright, alright. Show me the code.

First, we need to create an API Key:

meditations_api_key = apigateway_client.create_api_key(
    name="meditations",
    description="ohm",
    enabled=True,
    generateDistinctId=True,
)

Here, we create a named key that is enabled and we let API Gateway generate the key value for us. We could pass our own value as well.

Then, we'll create a Usage Plan:

meditations_usage_plan = apigateway_client.create_usage_plan(
    name="meditations",
    description="ohm",
    apiStages=[
        {
            "apiId": api["id"],
            "stage": "prod",
        },
    ],
    throttle={
        "burstLimit": 250,
        "rateLimit": 100,
    },
    quota={
        "limit": 10000,
        "period": "DAY",
    }
)

We've created a usage plan for our meditations API for our and prod stage. We've set the per-second request limit to 100 (rateLimit) and given some padding to 250 for bursting (burstLimit). The way these work are documented by AWS', but I didn't have a clear sense of over what time period the burst limit applied. We limit the users of our usage plan to 10,000 requests per day. You can also specify by week and month.

Note that usage is by plan, not by key. You can have many keys associated with a usage plan. Speaking of which:

meditations_usage_plan_key = apigateway_client.create_usage_plan_key(
    usagePlanId=meditations_usage_plan["id"],
    keyId=meditations_api_key["id"],
    keyType="API_KEY",
)

We need to associate our key with our usage plan. keyType is required but only assumes the value API_KEY.

We're now very close to getting API keys up running. The last step is to require the endpoint to use an API key. Unfortunately, I can't figure out how to get this done with swagger.

So, we'll brute force it with boto:

meditations_resources = apigateway_client.get_resources(restApiId=api["id"])

main_resource = [res for res in meditations_resources["items"] if res["path"] == "/"][0]

apigateway_client.update_method(
    restApiId=api["id"],
    resourceId=main_resource["id"],
    httpMethod="POST",
    patchOperations=[
        {
            "op": "replace",
            "path": "/apiKeyRequired",
            "value": "true",
        }
    ]
)

A hack, for sure. But better than nothing. Or the console...

Alternatively, go the console, API Gateway > APIs > Meditations > / > POST > Method Request > API Key Required. Flip that to true and save.

If anyone knows how to do this via swagger, drop me a line.

BEHOLD!

$ curl -X POST https://a1b2c3d4.execute-api.us-east-1.amazonaws.com/prod/
{"message": "Forbidden"}
$ curl -X POST -H "x-api-key: zM9Z0CNeM25UeMprorUJC8JTo2ypYj1Z2yR2Jwi7" https://a1b2c3d4.execute-api.us-east-1.amazonaws.com/prod/
{"status": "success", "meditation": "testing is broken because I'm lazy"}

And that's all there is to it.