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.