mirror of https://github.com/tiangolo/fastapi.git
📝 Add documentation for Behind a Proxy - Proxy Forwarded Headers, using `--forwarded-allow-ips="*"` (#14028)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
176cd8c9ef
commit
f5b77ff0fc
|
|
@ -1,6 +1,105 @@
|
||||||
# Behind a Proxy { #behind-a-proxy }
|
# Behind a Proxy { #behind-a-proxy }
|
||||||
|
|
||||||
In some situations, you might need to use a **proxy** server like Traefik or Nginx with a configuration that adds an extra path prefix that is not seen by your application.
|
In many situations, you would use a **proxy** like Traefik or Nginx in front of your FastAPI app.
|
||||||
|
|
||||||
|
These proxies could handle HTTPS certificates and other things.
|
||||||
|
|
||||||
|
## Proxy Forwarded Headers { #proxy-forwarded-headers }
|
||||||
|
|
||||||
|
A **proxy** in front of your application would normally set some headers on the fly before sending the requests to your **server** to let the server know that the request was **forwarded** by the proxy, letting it know the original (public) URL, including the domain, that it is using HTTPS, etc.
|
||||||
|
|
||||||
|
The **server** program (for example **Uvicorn** via **FastAPI CLI**) is capable of interpreting these headers, and then passing that information to your application.
|
||||||
|
|
||||||
|
But for security, as the server doesn't know it is behind a trusted proxy, it won't interpret those headers.
|
||||||
|
|
||||||
|
/// note | Technical Details
|
||||||
|
|
||||||
|
The proxy headers are:
|
||||||
|
|
||||||
|
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
|
||||||
|
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
|
||||||
|
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
### Enable Proxy Forwarded Headers { #enable-proxy-forwarded-headers }
|
||||||
|
|
||||||
|
You can start FastAPI CLI with the *CLI Option* `--forwarded-allow-ips` and pass the IP addresses that should be trusted to read those forwarded headers.
|
||||||
|
|
||||||
|
If you set it to `--forwarded-allow-ips="*"` it would trust all the incoming IPs.
|
||||||
|
|
||||||
|
If your **server** is behind a trusted **proxy** and only the proxy talks to it, this would make it accept whatever is the IP of that **proxy**.
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ fastapi run --forwarded-allow-ips="*"
|
||||||
|
|
||||||
|
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Redirects with HTTPS { #redirects-with-https }
|
||||||
|
|
||||||
|
For example, let's say you define a *path operation* `/items/`:
|
||||||
|
|
||||||
|
{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *}
|
||||||
|
|
||||||
|
If the client tries to go to `/items`, by default, it would be redirected to `/items/`.
|
||||||
|
|
||||||
|
But before setting the *CLI Option* `--forwarded-allow-ips` it could redirect to `http://localhost:8000/items/`.
|
||||||
|
|
||||||
|
But maybe your application is hosted at `https://mysuperapp.com`, and the redirection should be to `https://mysuperapp.com/items/`.
|
||||||
|
|
||||||
|
By setting `--proxy-headers` now FastAPI would be able to redirect to the right location. 😎
|
||||||
|
|
||||||
|
```
|
||||||
|
https://mysuperapp.com/items/
|
||||||
|
```
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
If you want to learn more about HTTPS, check the guide [About HTTPS](../deployment/https.md){.internal-link target=_blank}.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
### How Proxy Forwarded Headers Work
|
||||||
|
|
||||||
|
Here's a visual representation of how the **proxy** adds forwarded headers between the client and the **application server**:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Client
|
||||||
|
participant Proxy as Proxy/Load Balancer
|
||||||
|
participant Server as FastAPI Server
|
||||||
|
|
||||||
|
Client->>Proxy: HTTPS Request<br/>Host: mysuperapp.com<br/>Path: /items
|
||||||
|
|
||||||
|
Note over Proxy: Proxy adds forwarded headers
|
||||||
|
|
||||||
|
Proxy->>Server: HTTP Request<br/>X-Forwarded-For: [client IP]<br/>X-Forwarded-Proto: https<br/>X-Forwarded-Host: mysuperapp.com<br/>Path: /items
|
||||||
|
|
||||||
|
Note over Server: Server interprets headers<br/>(if --forwarded-allow-ips is set)
|
||||||
|
|
||||||
|
Server->>Proxy: HTTP Response<br/>with correct HTTPS URLs
|
||||||
|
|
||||||
|
Proxy->>Client: HTTPS Response
|
||||||
|
```
|
||||||
|
|
||||||
|
The **proxy** intercepts the original client request and adds the special *forwarded* headers (`X-Forwarded-*`) before passing the request to the **application server**.
|
||||||
|
|
||||||
|
These headers preserve information about the original request that would otherwise be lost:
|
||||||
|
|
||||||
|
* **X-Forwarded-For**: The original client's IP address
|
||||||
|
* **X-Forwarded-Proto**: The original protocol (`https`)
|
||||||
|
* **X-Forwarded-Host**: The original host (`mysuperapp.com`)
|
||||||
|
|
||||||
|
When **FastAPI CLI** is configured with `--forwarded-allow-ips`, it trusts these headers and uses them, for example to generate the correct URLs in redirects.
|
||||||
|
|
||||||
|
## Proxy with a stripped path prefix { #proxy-with-a-stripped-path-prefix }
|
||||||
|
|
||||||
|
You could have a proxy that adds a path prefix to your application.
|
||||||
|
|
||||||
In these cases you can use `root_path` to configure your application.
|
In these cases you can use `root_path` to configure your application.
|
||||||
|
|
||||||
|
|
@ -10,8 +109,6 @@ The `root_path` is used to handle these specific cases.
|
||||||
|
|
||||||
And it's also used internally when mounting sub-applications.
|
And it's also used internally when mounting sub-applications.
|
||||||
|
|
||||||
## Proxy with a stripped path prefix { #proxy-with-a-stripped-path-prefix }
|
|
||||||
|
|
||||||
Having a proxy with a stripped path prefix, in this case, means that you could declare a path at `/app` in your code, but then, you add a layer on top (the proxy) that would put your **FastAPI** application under a path like `/api/v1`.
|
Having a proxy with a stripped path prefix, in this case, means that you could declare a path at `/app` in your code, but then, you add a layer on top (the proxy) that would put your **FastAPI** application under a path like `/api/v1`.
|
||||||
|
|
||||||
In this case, the original path `/app` would actually be served at `/api/v1/app`.
|
In this case, the original path `/app` would actually be served at `/api/v1/app`.
|
||||||
|
|
@ -73,7 +170,7 @@ To achieve this, you can use the command line option `--root-path` like:
|
||||||
<div class="termy">
|
<div class="termy">
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ fastapi run main.py --root-path /api/v1
|
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
||||||
|
|
||||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||||
```
|
```
|
||||||
|
|
@ -103,7 +200,7 @@ Then, if you start Uvicorn with:
|
||||||
<div class="termy">
|
<div class="termy">
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ fastapi run main.py --root-path /api/v1
|
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
||||||
|
|
||||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||||
```
|
```
|
||||||
|
|
@ -224,7 +321,7 @@ And now start your app, using the `--root-path` option:
|
||||||
<div class="termy">
|
<div class="termy">
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ fastapi run main.py --root-path /api/v1
|
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
||||||
|
|
||||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,38 @@ To do that, and to accommodate different application needs, there are several wa
|
||||||
|
|
||||||
All this renewal process, while still serving the app, is one of the main reasons why you would want to have a **separate system to handle HTTPS** with a TLS Termination Proxy instead of just using the TLS certificates with the application server directly (e.g. Uvicorn).
|
All this renewal process, while still serving the app, is one of the main reasons why you would want to have a **separate system to handle HTTPS** with a TLS Termination Proxy instead of just using the TLS certificates with the application server directly (e.g. Uvicorn).
|
||||||
|
|
||||||
|
## Proxy Forwarded Headers { #proxy-forwarded-headers }
|
||||||
|
|
||||||
|
When using a proxy to handle HTTPS, your **application server** (for example Uvicorn via FastAPI CLI) doesn't known anything about the HTTPS process, it communicates with plain HTTP with the **TLS Termination Proxy**.
|
||||||
|
|
||||||
|
This **proxy** would normally set some HTTP headers on the fly before transmitting the request to the **application server**, to let the application server know that the request is being **forwarded** by the proxy.
|
||||||
|
|
||||||
|
/// note | Technical Details
|
||||||
|
|
||||||
|
The proxy headers are:
|
||||||
|
|
||||||
|
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
|
||||||
|
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
|
||||||
|
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
Nevertheless, as the **application server** doesn't know it is behind a trusted **proxy**, by default, it wouldn't trust those headers.
|
||||||
|
|
||||||
|
But you can configure the **application server** to trust the *forwarded* headers sent by the **proxy**. If you are using FastAPI CLI, you can use the *CLI Option* `--forwarded-allow-ips` to tell it from which IPs it should trust those *forwarded* headers.
|
||||||
|
|
||||||
|
For example, if the **application server** is only receiving communication from the trusted **proxy**, you can set it to `--forwarded-allow-ips="*"` to make it trust all incoming IPs, as it will only receive requests from whatever is the IP used by the **proxy**.
|
||||||
|
|
||||||
|
This way the application would be able to know what is its own public URL, if it is using HTTPS, the domain, etc.
|
||||||
|
|
||||||
|
This would be useful for example to properly handle redirects.
|
||||||
|
|
||||||
|
/// tip
|
||||||
|
|
||||||
|
You can learn more about this in the documentation for [Behind a Proxy - Enable Proxy Forwarded Headers](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank}
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## Recap { #recap }
|
## Recap { #recap }
|
||||||
|
|
||||||
Having **HTTPS** is very important, and quite **critical** in most cases. Most of the effort you as a developer have to put around HTTPS is just about **understanding these concepts** and how they work.
|
Having **HTTPS** is very important, and quite **critical** in most cases. Most of the effort you as a developer have to put around HTTPS is just about **understanding these concepts** and how they work.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/items/")
|
||||||
|
def read_items():
|
||||||
|
return ["plumbus", "portal gun"]
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from docs_src.behind_a_proxy.tutorial001_01 import app
|
||||||
|
|
||||||
|
client = TestClient(
|
||||||
|
app,
|
||||||
|
base_url="https://example.com",
|
||||||
|
follow_redirects=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_redirect() -> None:
|
||||||
|
response = client.get("/items")
|
||||||
|
assert response.status_code == 307
|
||||||
|
assert response.headers["location"] == "https://example.com/items/"
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_redirect() -> None:
|
||||||
|
response = client.get("/items/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == ["plumbus", "portal gun"]
|
||||||
Loading…
Reference in New Issue