FastAPI TrustedHostMiddleware refuses my host
Asked Answered
F

1

1

When I was developing the application on my local machine and I tried to reach /docs I had to set up up 127.0.0.1 as my trusted host. This worked fine. But now I have put the app on the server and when I try to reach /docs with the IP of my computer, I get denied.

I have checked my ip on several websites, I have also checked request.client.host to confirm the IP. But I can not access the /docs.

The only way I can access route is, if I set trusted_hosts = ["*"] But this doesn't make sense.

Here is my code:

from fastapi import FastAPI
from fastapi.middleware.trustedhost import TrustedHostMiddleware

app = FastAPI()

# Define a list of trusted hosts
trusted_hosts = [
    "my_ip_address"
]

# Add TrustedHostMiddleware to the app with the list of trusted hosts
app.add_middleware(TrustedHostMiddleware, allowed_hosts=trusted_hosts)
Fortyfour answered 14/4 at 11:14 Comment(2)
I think there should be the IP of your server and not your personal computer instead!Masinissa
But this way everyone can access the API?Fortyfour
H
1

The TrustedHostMiddleware (see the relevant Starlette's documentation as well) "enforces that all incoming requests have a correctly set Host header, in order to guard against HTTP Host Header attacks".

The Host header is not the client's IP address, but the hostname, such as localhost, example.com, *.example.com and so on (without including the port number)—it specifies the domain name that the client wants to access.. Hence, when using the TrustedHostMiddleware, FastAPI simply checks the Host header (if present)—which, by the way, could easily be spoofed by an attackcer in the HTTP request, in order to make it look like the request is coming from a different domain (for instance, one that is allowed by your server)—it does not check the client's IP address. If, for example, you edited the /etc/hosts file on your system to assign a hostname to 127.0.0.1 such as example.test (see Solution 1 of this answer on how to do that), and then attempted to access the API by typing example.test:8000 in the address bar of your browser, the Host header would be example.test instead of localhost. Working example is given below.

Working Example

from fastapi import FastAPI, Request
from fastapi.middleware.trustedhost import TrustedHostMiddleware


app = FastAPI()
app.add_middleware(
    TrustedHostMiddleware, allowed_hosts=["localhost", "example.test"]
)   


@app.get("/")
async def main(request: Request):
    print(request.headers['host'])
    return {"message": "Hello World"}

Limit API access only to specific IP addresses

In order to limit access to your API only to IP addresses of your choice, you should rather follow a similar approach to this, where a safe_clients list is maintained and the IP address of each request is checked against that list.

Working Example

from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse


app = FastAPI()
TRUSTED_IPS = ["127.0.0.1"]


@app.middleware('http')
async def trusted_ip_middleware(request: Request, call_next):
    ip = str(request.client.host)
    print(ip)
    
    if ip not in TRUSTED_IPS:
        return JSONResponse(content="403 - Forbidden: Access is denied", status_code=403)

    return await call_next(request)
    

@app.get("/")
async def main():
    return {"message": "Hello World"}

Note that, as explained in this answer, if you are running behind a reverse proxy server such as nginx, request.client.host would return the IP of the reverse proxy instead, and thus, you should use the X-Forwarded-For header, in order get the client's IP address. Please refer to the linked answer above, as well as this answer and Uvicorn's ProxyHeadersMiddleware (find this on github), for more details.

Historiography answered 14/4 at 14:11 Comment(2)
Christ thank you very much. So what is then the best practice to allow incoming requests from one domain name and as well from one IP address? Chaining Middlewares doesn't seem O.K, because TrustedHost will block me before I would come to check safe_ip and vice versa. I would really appreciate your reply.Fortyfour
There is nothing wrong with using more than one middleware in your application (as well as it might be more suitable to have the "Trusted IP" middleware separated from the "Trusted Host" one). In fact, when building real-world applications, it is most likely that you would end up using multiple middlewares, reagrdless. If, however, you insist on using a single middleware, I would suggest having a look at the implementation of the TrustedHostMiddleware and copy its logic into the one implemented above.Historiography

© 2022 - 2024 — McMap. All rights reserved.