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.
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
$ 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:
Add the import for Depends and Security to the
fastapi import statement.
from fastapi import FastAPI, Depends, Security # ...
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
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
# ... 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, and the header's name. Put this code above the
# ... 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.
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
In the final article, we'll talk about deploying to Heroku. Thanks for reading!