Introduction
Sometimes the biggest obstacle when building a new app or project is security. It seems that each technology has its own special set of concerns. How can we be sure that we're protecting our resources? I was using NodeJS and setting up an express server the first time I used middleware.
Thankfully, NodeJS wasn't that new. There was plenty of easy libraries to add quick ways to protect your routes.
Similarly, although FastAPI isn't old, there are quick ways that we can prevent unnecessary or unauthorized use of our API. In this article, we'll take a look at some of those methods.
What is Middleware?
Middleware works in between the server and the routes. We can use it to reject requests, add headers, remove data from response objects, process, analyze, and track the flow of requests to all of our routes or some of our routes.
You can do many things with middleware, but a few common use cases include:
- CORS
- Authentication
- Authorization
- Adding headers
- Adding properties to request
Adding Middleware
In FastAPI, we add middleware to the app with the add_middleware
function. FastAPI covers some basic use cases that we can add with little configuration.
First, let's add some middleware that checks the host header on incoming requests.
from fastapi.middleware.trustedhost import TrustedHostMiddleware
# ...
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["localhost", "app.herokuapp.com"],
)
We imported the middleware from fastapi.middleware
. This is a library that provides some plug-and-play options.
Then, we passed the imported object as the first argument to the function. After that, depending on the middleware, we have configuration options.
In the above example, this means adding the two domains that are correct hosts in production and development. Granted, localhost should not be used as a valid host in production.
CORS
Cross-origin-resource-sharing declares what resources can be shared between browsers or different domains.
We can set up a global CORS configuration with such a small API and not worry about blocking important access.
CORS 'allowed origins' often change with the environment.
from fastapi.middleware.cors import CORSMiddleware
#...
origins = dict(
development=["http://localhost:8000"],
production=["https://production.url"]
)
app.add_middleware(
CORSMiddleware,
allow_origins=origins[settings.ENV],
allow_credentials=True,
allow_methods=["GET"],
allow_headers=["*"],
)
Therefore, we are changing the allowed origin (where the request is coming from) based on whether we are in production or development (more on this in a little bit).
allow_credentials
specifies whether or not we are accepting cookies in the request.allow_methods
details the HTTP request methods that are allowed for the resource.allow_headers
is—intuitively—the headers that the resource will accept.
CORS will not be applied for requests sent from API clients (like Postman) or mobile devices.
OK! We have already secured our API in a couple of important ways. However, let's take a quick break to explain how we set environment variables in FastAPI.
Environment Variable
First, we need to create a class to set the types for our environment variables.
from pydantic import BaseModel, BaseSettings
#... before app is declared
class Settings(BaseSettings):
SANITY_API_KEY: str
API_KEY: str
ENV: str = "production"
I declare the Settings
class just like any other typed object. However, instead of passing in the BaseModel
class, I pass in the BaseSettings
class.
I can set a default value (like I did for ENV)
just like I would set a default value in a response or request object. If no environment string is passed, I want the app to execute like it would in "production"
.
Next, we build the object with the relevant values by calling the Settings
class and initializing a settings
object.
# this has to be done after we declare the class Settings
settings = Settings()
app = FastAPI(
# ...
)
But how do we pass in the variable values?
A simple way to declare environment variables is at the time we start the server.
$ SANITY_API_KEY=123 API_KEY=123 ENV=development uvicorn main:app --reload
It really is that easy. With a service like Heroku, you can inject these values as secrets through their cloud platform. That allows developers to not hardcode these values, like an API key into code.
Conclusion
This was a very quick middleware and environment variables article. With FastAPI, things are pretty simple, thus making it easy to explain and set up.
In the next article, I'll show how to check an API key header for a secret key.