fastapistarlette

How to get custom middleware to run before AuthenticationMiddleware?


I'm sending an id_token in my cookies to a FastAPI application that also has a mounted Starlette app. I have a CustomMiddleware class that I'd like to run before the AuthenticationMiddleware for token validation. However, the AuthenticationMiddleware always runs first:

import uvicorn
from fastapi import FastAPI
from starlette.applications import Starlette
from starlette.authentication import (
    AuthCredentials,
    AuthenticationBackend,
    BaseUser,
)
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
from starlette.routing import Route


class AuthenticatedUser(BaseUser):
    def __init__(self, display_name: str) -> None:
        self._display_name = display_name

    @property
    def is_authenticated(self) -> bool:
        return True


class AuthBackend(AuthenticationBackend):
    async def authenticate(self, conn):
        print("Authenticate method fired")
        return AuthCredentials(["authenticated"]), AuthenticatedUser(
            display_name="Test User"
        )


class CustomHeaderMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        response = await call_next(request)
        print("custom middleware fired")
        return response


middleware = [
    Middleware(CustomHeaderMiddleware),
    Middleware(AuthenticationMiddleware, backend=AuthBackend()),
]


async def starlette_homepage(request):
    return JSONResponse({"message": "Hello from Starlette!"})


starlette_app = Starlette(
    debug=True,
    routes=[
        Route("/", starlette_homepage),
    ],
)

app = FastAPI(middleware=middleware)


@app.get("/")
async def fastapi_homepage():
    return {"message": "Hello from FastAPI!"}


app.mount("/starlette", starlette_app)

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8000)

Running this and going to localhost:8000/ or localhost:8000/starlette I see:

INFO:     127.0.0.1:51118 - "GET / HTTP/1.1" 200 OK
Authenticate method fired
custom middleware fired
INFO:     127.0.0.1:51129 - "GET /starlette HTTP/1.1" 307 Temporary Redirect
Authenticate method fired
custom middleware fired
INFO:     127.0.0.1:51129 - "GET /starlette/ HTTP/1.1" 200 OK

How can I ensure that the CustomMiddlewareruns first so I can perform token validation before running the AuthenticationMiddleware and creating the AuthenticatedUser?


Solution

  • In CustomHeaderMiddleware you print the message after calling await call_next(request).

    Change it to print before await call_next(request) and everything will work:

    class CustomHeaderMiddleware(BaseHTTPMiddleware):
        async def dispatch(self, request, call_next):
            print("custom middleware fired")
            response = await call_next(request)
            return response
    

    If you need to change the order of middlewares, just change their order in the list of middlewares