How to get the origin URL in FastAPI?

Question:

Is it possible to get the URL that a request came from in FastAPI?

For example, if I have an endpoint that is requested at api.mysite.com/endpoint and a request is made to this endpoint from www.othersite.com, is there a way that I can retrieve the string "www.othersite.com" in my endpoint function?

Asked By: KOB

||

Answers:

The premise of the question, which could be formulated as

a server can identify the URL from where a request came

is misguided. True, some HTTP requests (especially some of the requests issued by browsers) carry an Origin header and/or a Referer [sic] header. Also, the Forwarded header, if present, contains information about where the request was issued. However, nothing in the HTTP specification requires that requests in general advertise where they came from.

Therefore, whether with FastAPI or some other server technology, there’s no definite way of knowing where a request came from.

Answered By: jub0bs

Update

As mentioned by @jub0bs, HTTP requests usually carry the Referer header that contains the address from which a resource has been requested. As per MDN’s documentation:

The Referer HTTP request header contains the absolute or partial
address from which a resource has been requested. The Referer header
allows a server to identify referring pages that people are
visiting from or where requested resources are being used. This data
can be used for analytics, logging, optimized caching, and more.

When you click a link, the Referer contains the address of the page
that includes the link. When you make resource requests to another
domain, the Referer contains the address of the page that uses the
requested resource
.

In FastAPI, you can retrieve the request headers, as demonstrated here. Hence, you can obtain the Referer URL in the following way:

from fastapi import FastAPI, Request

app = FastAPI()

@app.get('/')
def main(request: Request):
    referer = request.headers.get('referer')
    return referer

Original Answer

As per FastAPI documentation, and hence Starlette’s:

Let’s imagine you want to get the client’s IP address/host inside of
your path operation function.

@app.get("/items/{item_id}")
def read_root(item_id: str, request: Request):
    client_host = request.client.host
    return {"client_host": client_host, "item_id": item_id}

Please note that if you are running behind a reverse proxy server such as nginx, you would need to run Uvicorn with --proxy-headers flag to accept such headers (it is already enabled by default, but is restricted to only trusting connecting IPs in the forwarded-allow-ips configuration), as well as with --forwarded-allow-ips='*' flag to ensure that the domain socket is trusted as a source from which to proxy headers (instead of trusting headers from all IPs using the '*' wildcard, it would be more safe to trust only proxy headers from the IP of your reverse proxy server). As per Uvicorn’s docs:

  • --proxy-headers / --no-proxy-headers – Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info. Defaults to enabled, but is restricted to only trusting connecting IPs in the forwarded-allow-ips configuration.

  • --forwarded-allow-ips – Comma separated list of IPs to trust with proxy headers. Defaults to the $FORWARDED_ALLOW_IPS environment variable if available, or '127.0.0.1'. A wildcard '*' means always trust.

To ensure that the proxy forwards them, you should make sure that the X-Forwarded-For and X-Forwarded-Proto headers are set by the proxy (see Uvicorn’s documentation, as well as this post for more details).

Assuming that requests to your API are handled in the backend of the website that you are trying to retrieve its URL/domain, and not by allowing users to issue requests to your API directly from their frontend—and hence, in that case, the client’s IP address would be the website’s (server/backend) IP address, and not the user’s IP address, who is browsing the website—once you obtain the website’s IP address (using request.client.host, as described earlier), you can perform a reverse DNS lookup to get the website’s hostname (doesn’t always exist though, or does not have a meaningful name), as shown in the example below. From there, you can look up for information on the hostname or the IP address itself online. You could also create a database with every IP address (or better, hostname) you resolve for future reference.

import socket
#address = '2001:4860:4860::8888' # Google's Public DNS IPv6 address
address = '216.58.214.14' # a Google's IP address
print(socket.gethostbyaddr(address)[0])
Answered By: Chris