Top Rated Plus on Upwork with a 100% Job Success ScoreView on Upwork
retzdev logo
logo
Building a Python API with FastAPI

Adding API Key Authorization

by Jarrett Retz

May 6th, 2021

Introduction

In the previous article, I introduced middleware and how to add it to FastAPI. Also, that brought the process of adding environment variables into the FastAPI app.

A common environment variable used in server-side programming is API keys. These keys can authorize access to the server endpoints or are required for calling third-party APIs. Furthermore, API keys often are secret. So, it's best practice not to hardcode the value or commit the value to a repository.

In this article, let's pass an API key into our app and use it to protect a route.

Environment Variables

To recap, we pass in environment variables when we start the server. Later, even these values (passed in on the command-line) will be variables pulled in from the environment. Again, this to protect those values from ending up in server logs or histories.

For now, let's throw in some arbitrary key values to see how this works. Start the app by passing in an API_KEY variable.

$ SANITY_API_KEY=123 API_KEY=123 ENV=development uvicorn main:app --reload

We'll be pulling this value off of the settings object that we initialize before creating the app.

# this has to be done after we declare the class Settings

settings = Settings()

app = FastAPI(
  # ...
)

Import Typing Objects

At the top of the file, import two built-in types to use for the key.

from fastapi.security.api_key import APIKeyHeader, APIKey
 #...

You could also pull the API key from the cookies of the request, but we're going to use an Authorization header.

Import Helper Functions

FastAPI provides two functions to help us manage security:

  1. Depends()
  2. Security()

Add the import for Depends and Security to the fastapi import statement.

from fastapi import FastAPI, Depends, Security
# ...

Protect Route

The Depends function lets us tell the app that the endpoint needs a parameter; it depends on it. If the parameter is not there, it can't execute.

# ... /article/related
async def get_related_articles(
    id: str = Query(..., title="The ID of the article to find related articles for."),
    api_key: APIKey = Depends(get_api_key),
):
  # ... function code

In the code above, we add a parameter to this function called api_key. This parameter is defined in the handler function as the route /article/related.

Notice that we pass a function in. This function get_api_key needs to return the API key value. However, this function is not defined.

Before the first route (index route), add the get_api_key function.

# ...

async def get_api_key(
    api_key_header: str = Security(api_key_header),
):
    if api_key_header == API_KEY:
        return api_key_header
    else:
        raise HTTPException(status_code=403)
        
# ... routes

The Security function helps retrieve the api_key_function from the request.

This is read as, if the API key pulled from the Authorization header is equal to the API key defined as an environment variable, return the value. Else, return an unsuccessful status code.

You may have noticed that we're working through this backward. I think that's a better way of explaining it. The final piece of the puzzle is declaring the api_key_header variable, API_KEY, and the header's name. Put this code above the get_api_key function.

# ...
API_KEY = settings.API_KEY
API_KEY_NAME = "Authorization"
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=True)

# ... get_api_key function

Now, the route is protected by the API key header! We can quickly check the authorization by visiting the /docs URL on port 8000. We see an Authorize button and a lock next to the protected route.

Clicking on the lock icon renders the authorization options popup.

We can view the route and "Try it Out" after entering the API key specified when we started the app.

Conclusion

This is not quite an ideal authorization workflow, and FastAPI has other options for securing APIs and routes.

However, this is well on its way to being a viable option. The biggest problem is the API key should use some encryption. Then, it should be decrypted in the get_api_key function.

In the final article, we'll talk about deploying to Heroku. Thanks for reading!

Jarrett Retz

Jarrett Retz is a freelance web application developer and blogger based out of Spokane, WA.

jarrett@retz.dev

Subscribe to get instant updates

Contact

jarrett@retz.dev

Legal

Any code contained in the articles on this site is released under the MIT license. Copyright 2024. Jarrett Retz Tech Services L.L.C. All Rights Reserved.