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

Middleware and Security in FastAPI

by Jarrett Retz

April 22nd, 2021

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.

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.