Search code examples
jwtfastapihttp-authentication

FastAPI auth check before granting access to sub-applications


I am mounting a Flask app as a sub-application in my root FastAPI app, as explained in the documentation

Now I want to add an authentication layer using HTTPAuthorizationCredentials dependency, as nicely explained in this tutorial

tutorial code

How can I do that?

Preferably, I would like that any type of access attempt to my Flask sub-application goes first through a valid token authentication process implemented in my FastAPI root app. Is that possible?


Solution

  • You can use a custom WSGIMiddleware and authorize the call to flask app inside that like this:

    from fastapi import FastAPI, Depends, HTTPException
    from fastapi.middleware.wsgi import WSGIMiddleware
    from flask import Flask, escape, request
    from starlette.routing import Mount
    from starlette.types import Scope, Receive, Send
    
    flask_app = Flask(__name__)
    
    def authenticate(authorization: str = Header()):
        # Add logic to authorize user
        if authorization == "VALID_TOKEN":
            return
        else:
            raise HTTPException(status_code=401, detail="Not Authorized")
    
    class AuthWSGIMiddleware(WSGIMiddleware):
    
        async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
            _, authorization = next((header for header in scope['headers'] if header[0] == b'authorization'), (b'authorization', "" ))
            authenticate(authorization.decode('utf-8'))
            await super().__call__(scope, receive, send)
    
    routes = [
                Mount("/v1", AuthWSGIMiddleware(flask_app)),
            ]
    
    # OR Optionally use this as you were doing
    # The above one is preferred as per starlette docs
    # app.mount("/v1", WSGIMiddleware(flask_app))
    
    
    @flask_app.route("/")
    def flask_main():
        name = request.args.get("name", "World")
        return f"Hello, {escape(name)} from Flask!"
    
    app = FastAPI(routes=routes, dependencies=[Depends(authenticate)])
    
    
    @app.get("/v2")
    def read_main():
        return {"message": "Hello World"}
    
    

    For the tutorial you are looking at, you can replace the invoking authenticate function in my example inside AuthWSGIMiddleware->__call__() with

    AuthHandler().decode_toke(authorization)