Background
API keys, also known as application programming interface keys, are codes or tokens used to authenticate and authorize access to an API (Application Programming Interface).
The API provider typically issues API keys to developers who need to integrate with the API. The API key serves as a unique identifier to track and limit the API's usage and enforce any rate limits or other access controls.
Recently, I found myself in a situation where I needed to write integration tests for an API endpoint protected by API keys. Unfortunately, generating an API key for a test and using it for authorization was not well-documented. After some research and experimentation, I eventually found a methodology that worked well.
Situation
The API was built using Django and Django REST Framework (DRF). API keys were implemented using the DRF API Keys module. The testing library was pytest.
Solution
The solution that I found is shown below:
def test_if_has_api_key_no_payload_returns_400(self, api_client):
# Programatically generate an API key using the APIKey's convenience
# function, create_key. create_key returns a tuple with the APIKey
# object and the plaintext API key. This is important, because the
# APIKey object only contains a hashed value of the key. We have no
# other way of getting the plaintext key, which we need to authenticate
# with the endpoint.
_, key = APIKey.objects.create_key(name="name")
# Initially, I tried posting to the endpoint with the API key in a header
# dictionary, like when using the requests module. After much head-
# scratching, debugging, and research, I found that the DRF APIClient
# expects headers in a slightly different format. I also found that
# the header the application expects, Authorization, is expected
# to be called HTTP_AUTHORIZATION.
res = api_client.post(
self._endpoint,
HTTP_AUTHORIZATION=f"Api-Key {key}",
)
assert res.status_code == status.HTTP_400_BAD_REQUEST
The APIClient is not well documented, according to this helpful person on StackOverflow: https://stackoverflow.com/a/53085606/19300652
And, well...that's all there is to it!