From 4a4977f901cb7ff855934d7838ff4741abdf6b6a Mon Sep 17 00:00:00 2001 From: Nils Lindemann Date: Mon, 1 Sep 2025 03:12:51 +0200 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 3e2dbf91699ef2e3a713372f099ef278b3bcc59e Author: github-actions[bot] Date: Sun Aug 31 19:34:37 2025 +0000 📝 Update release notes [skip ci] commit f5b77ff0fcd8e191d9be98d61292ecfaa6cb89a6 Author: Sebastián Ramírez Date: Sun Aug 31 21:34:08 2025 +0200 📝 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> commit 176cd8c9ef81c3e4c5ba826e7eb87554bae17080 Author: github-actions[bot] Date: Sun Aug 31 15:20:12 2025 +0000 📝 Update release notes [skip ci] commit 1884d76f613d951e2243e50a3df4d9a479f00a9d Author: Sebastián Ramírez Date: Sun Aug 31 17:19:49 2025 +0200 🔧 Update sponsors: remove Platform.sh (#14027) commit 8062aabdaa4e39a31bd01f3fb35428a3207de1bb Author: github-actions[bot] Date: Sun Aug 31 15:02:29 2025 +0000 📝 Update release notes [skip ci] commit ee9ccac1e5f5faca45a56e0e0eea109dfb2988a4 Author: Sebastián Ramírez Date: Sun Aug 31 17:02:08 2025 +0200 🔧 Update sponsors: remove Mobb (#14026) commit 5cd4c3b6bd3eec27fa3c621e4a2d2a2c00605057 Author: github-actions[bot] Date: Sun Aug 31 10:59:17 2025 +0000 📝 Update release notes [skip ci] commit 4584f706bdf833ede7e6d5771c0d16bb9c01fafd Author: Jom Karlo Verzosa Date: Sun Aug 31 18:58:56 2025 +0800 📝 Add deprecation info block about `dict()` in `docs/tutorial/body.md` (#13906) Co-authored-by: Sebastián Ramírez commit ba9c8fba0b26beccfc1300f9da1f4dea97e08157 Author: github-actions[bot] Date: Sun Aug 31 10:50:12 2025 +0000 📝 Update release notes [skip ci] commit d9249c1949b856f60bd2a79c94e43c61989a0af9 Author: Valentyn Date: Sun Aug 31 06:49:48 2025 -0400 📝 Fix Twitter to be X (Twitter) everywhere in documentation (#13809) Co-authored-by: Valentyn Druzhynin Co-authored-by: Sebastián Ramírez commit a973e787af25d4dc87ddd15ad2c0819437d08f6f Author: github-actions[bot] Date: Sun Aug 31 10:33:32 2025 +0000 📝 Update release notes [skip ci] commit 1088d2abd908b7101ad7ea4ed0a5e141307d7693 Author: Ashish Pandey <126683810+Ashish-Pandey62@users.noreply.github.com> Date: Sun Aug 31 16:17:57 2025 +0545 🐛 Prevent scroll-to-top on restart/fast buttons in `termynal.js` (#13714) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> commit 98ec6a6079f31aaf95814e1c1d6194d12bd9aaa9 Author: github-actions[bot] Date: Sun Aug 31 10:29:48 2025 +0000 📝 Update release notes [skip ci] commit 6b4d292f3a95709821429ee1b3dead1563c91c75 Author: github-actions[bot] Date: Sun Aug 31 10:29:27 2025 +0000 📝 Update release notes [skip ci] commit d4ddcc5878a6cccd9815e76e69ff1fb52bd52457 Author: z0z0r4 Date: Sun Aug 31 18:29:21 2025 +0800 📝 Update testing events documentation (#13259) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Co-authored-by: Sebastián Ramírez commit 0e5832aa6d8c9badd203c2d8be7ae1842bb3c9e4 Author: Hotah Ma Date: Sun Aug 31 18:29:01 2025 +0800 📝 Remove obsolete `url` field in error responses in docs (#13655) Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> commit ee2acd8abc7b63863be3ee6ef61e46878d3549b9 Author: github-actions[bot] Date: Sun Aug 31 10:03:35 2025 +0000 📝 Update release notes [skip ci] commit e902ed5fc68dc73b4fbc5ce3c4434e5c493683ee Author: Arnaud Durand Date: Sun Aug 31 12:03:10 2025 +0200 📝 Bring the `scope` claim in line with the standard in `docs_src/security/tutorial005.py` (#11189) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Yurii Motov commit cef1f166dfe326792001e2cfe0dfeeebe45e7cd3 Author: github-actions[bot] Date: Sun Aug 31 09:59:28 2025 +0000 📝 Update release notes [skip ci] commit 8e63f75919f1aca30d5dc62cfcf0d8c027e8c8cd Author: Soul Lee Date: Sun Aug 31 18:59:07 2025 +0900 📝 Update TrustedHostMiddleware Documentation (#11441) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Alejandra <90076947+alejsdev@users.noreply.github.com> Co-authored-by: Sofie Van Landeghem Co-authored-by: Sebastián Ramírez Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> commit e1b9cc00caa9729880cdea838706a22797932d31 Author: github-actions[bot] Date: Sun Aug 31 09:56:48 2025 +0000 📝 Update release notes [skip ci] commit 408b8a9bebf3af84943acf7b57bf91b0c5e76a25 Author: Denny Biasiolli Date: Sun Aug 31 11:56:21 2025 +0200 📝 Remove links to site callbackhell.com that doesn't exist anymore (#14006) commit 0817c955ec88cac09a8aec87b27c5acd14e65861 Author: github-actions[bot] Date: Sun Aug 31 09:16:03 2025 +0000 📝 Update release notes [skip ci] commit c55f7138a11e9d315db95c1a29711b0d36baa33d Author: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Sun Aug 31 11:15:41 2025 +0200 📝 Add permalinks to headers in English docs (#13993) Co-authored-by: Sebastián Ramírez commit 7653de2715c17feddfdc5621f9097aaae3e374b1 Author: github-actions[bot] Date: Sun Aug 31 09:11:36 2025 +0000 📝 Update release notes [skip ci] commit 784f068abaa57aa25eac104e6d831ca12980f90e Author: Sebastián Ramírez Date: Sun Aug 31 11:11:15 2025 +0200 🛠️ Update `mkdocs_hooks` to handle headers with permalinks when building docs (#14025) Co-authored-by: Yurii Motov commit 6db05770f6f42d6c3e96e5e228b8fc7447026ce3 Author: github-actions[bot] Date: Mon Aug 25 20:03:24 2025 +0000 📝 Update release notes [skip ci] commit 6be02e3d5261b1341a76d0e0f337ec5aa78f4e62 Author: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon Aug 25 22:03:02 2025 +0200 ⬆ [pre-commit.ci] pre-commit autoupdate (#14016) updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.9 → v0.12.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.9...v0.12.10) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> commit 9cf7b70d7b282b0671dbb1a93a4cbc31aac58abe Author: github-actions[bot] Date: Wed Aug 20 09:11:20 2025 +0000 📝 Update release notes [skip ci] commit f75c1532f612137795b291f42fb507f7f103807b Author: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Wed Aug 20 11:10:51 2025 +0200 ⬆ Bump `mkdocs-macros-plugin` from 1.3.7 to 1.3.9 (#14003) Bump mkdocs-macros-plugin from 1.3.7 to 1.3.9 commit 5c3a70d5b6def6f5ee471c3f9c7e6d2b2c6f6200 Author: github-actions[bot] Date: Mon Aug 18 21:07:25 2025 +0000 📝 Update release notes [skip ci] commit 6a45249303cf5ce8c9cde2c35497d2d12a4221a1 Author: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon Aug 18 23:07:04 2025 +0200 ⬆ [pre-commit.ci] pre-commit autoupdate (#13999) updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.8 → v0.12.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.8...v0.12.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> commit c23051682bbeb254faee28fe80beeeab1764f7c4 Author: github-actions[bot] Date: Mon Aug 18 06:35:06 2025 +0000 📝 Update release notes [skip ci] commit df779885fa094da506193a3772c6ab0335728790 Author: Mika <44454249+anfreshman@users.noreply.github.com> Date: Mon Aug 18 14:34:40 2025 +0800 📝 Fix code include for Pydantic models example in `docs/zh/docs/python-types.md` (#13997) Updated the Pydantic expiration example in the Chinese documentation --- docs/en/docs/advanced/behind-a-proxy.md | 107 +++++++++++++++++- docs/en/docs/deployment/https.md | 32 ++++++ docs/en/docs/release-notes.md | 1 + docs_src/behind_a_proxy/tutorial001_01.py | 8 ++ .../test_tutorial001_01.py | 21 ++++ 5 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 docs_src/behind_a_proxy/tutorial001_01.py create mode 100644 tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py diff --git a/docs/en/docs/advanced/behind-a-proxy.md b/docs/en/docs/advanced/behind-a-proxy.md index 0f100306a..e510e65e1 100644 --- a/docs/en/docs/advanced/behind-a-proxy.md +++ b/docs/en/docs/advanced/behind-a-proxy.md @@ -1,6 +1,105 @@ # 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: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +### 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**. + +
+ +```console +$ fastapi run --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### 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
Host: mysuperapp.com
Path: /items + + Note over Proxy: Proxy adds forwarded headers + + Proxy->>Server: HTTP Request
X-Forwarded-For: [client IP]
X-Forwarded-Proto: https
X-Forwarded-Host: mysuperapp.com
Path: /items + + Note over Server: Server interprets headers
(if --forwarded-allow-ips is set) + + Server->>Proxy: HTTP Response
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. @@ -73,7 +172,7 @@ To achieve this, you can use the command line option `--root-path` like:
```console -$ fastapi run main.py --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -103,7 +202,7 @@ Then, if you start Uvicorn with:
```console -$ fastapi run main.py --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -224,7 +323,7 @@ And now start your app, using the `--root-path` option:
```console -$ fastapi run main.py --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` diff --git a/docs/en/docs/deployment/https.md b/docs/en/docs/deployment/https.md index b52ed40c8..a249a3672 100644 --- a/docs/en/docs/deployment/https.md +++ b/docs/en/docs/deployment/https.md @@ -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). +## 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: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +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 } 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. diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 128ace37b..d669b5dd0 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Docs +* 📝 Add documentation for Behind a Proxy - Proxy Forwarded Headers, using `--forwarded-allow-ips="*"`. PR [#14028](https://github.com/fastapi/fastapi/pull/14028) by [@tiangolo](https://github.com/tiangolo). * 📝 Add deprecation info block about `dict()` in `docs/tutorial/body.md`. PR [#13906](https://github.com/fastapi/fastapi/pull/13906) by [@jomkv](https://github.com/jomkv). * 📝 Fix Twitter to be X (Twitter) everywhere in documentation. PR [#13809](https://github.com/fastapi/fastapi/pull/13809) by [@valentinDruzhinin](https://github.com/valentinDruzhinin). * 🐛 Prevent scroll-to-top on restart/fast buttons in `termynal.js`. PR [#13714](https://github.com/fastapi/fastapi/pull/13714) by [@Ashish-Pandey62](https://github.com/Ashish-Pandey62). diff --git a/docs_src/behind_a_proxy/tutorial001_01.py b/docs_src/behind_a_proxy/tutorial001_01.py new file mode 100644 index 000000000..52b114395 --- /dev/null +++ b/docs_src/behind_a_proxy/tutorial001_01.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/") +def read_items(): + return ["plumbus", "portal gun"] diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py new file mode 100644 index 000000000..f13046e01 --- /dev/null +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py @@ -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"]