How to get the origin URL in FastAPI?
Asked Answered
C

2

4

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?

Conglomerate answered 12/1, 2022 at 16:40 Comment(0)
R
5

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.

Royo answered 12/1, 2022 at 20:26 Comment(2)
Thanks for the explanation on how it may always not be present. I ended up retrieving this in FastAPI like origin_url = dict(request.scope["headers"]).get(b"referer", b"").decode(). My requirement for this is to know whether the request is coming from our dev or prod frontend server, and, from testing, it appears that this header will always be there in our case.Conglomerate
Very nice explanation @jub0bs! It was very usefull to my researches.Aconite
I
0

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])
Inefficacious answered 12/1, 2022 at 22:50 Comment(2)
This will give the IP of the device that is running the site that makes the request to my API, not the domain of the site itself.Conglomerate
Thanks, but again, the problem here would be that the IP address is the IP address of the client device, not the domain that the client is browsing. In your example, you used Google as an example, but the IP adddress from request.client.host would not be Google's IP address but instead the device's IP address that is browsing www.google.com.Conglomerate

© 2022 - 2024 — McMap. All rights reserved.