Typorithm · medium

Host validation and CORS solve different problems

pythonfastapiindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Host validation and CORS solve different problems

0%

Concept and contract

Restrict accepted hosts and expose only an explicit browser-origin policy.

Host validation and CORS inspect different request information. TrustedHostMiddleware limits the Host header accepted by the application. CORS describes which browser origins may read a response under browser rules. Neither mechanism verifies the identity or object permissions of a caller.

The fixture accepts two synthetic hosts and one explicit HTTPS origin. Credentials are disabled and only GET is allowed by the CORS policy. The local route returns a small public object, so the test can inspect host rejection and browser-origin response headers independently.

Mechanism and worked example

A request with an allowed host reaches the endpoint.

A request with an allowed host reaches the endpoint. If its Origin matches the configured origin, the response includes the corresponding access-control-allow-origin value. A nonmatching origin can still receive an ordinary server response while lacking that browser permission header.

The helper returns status, actual body and the allow-origin header. A host failure is plain text rather than the endpoint JSON, so the test handles each representation deliberately. There is no real DNS lookup or browser navigation; TestClient sends the synthetic headers in process.

Complete worked example

python

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.testclient import TestClient

def inspect_example(request):
    app = FastAPI()
    app.add_middleware(CORSMiddleware, allow_origins=["https://app.example.test"],
                       allow_credentials=False, allow_methods=["GET"], allow_headers=[])
    app.add_middleware(TrustedHostMiddleware, allowed_hosts=["testserver", "api.example.test"])
    @app.get("/")
    def read():
        return {"ok": True}
    with TestClient(app) as client:
        response = client.get("/", headers={"Host": request.get("host", "testserver"),
                              "Origin": request.get("origin", "https://app.example.test")})
        return {"status": response.status_code,
                "body": response.json() if response.status_code == 200 else response.text,
                "allow": response.headers.get("access-control-allow-origin")}
Reasoning
A request with an allowed host reaches the endpoint. If its Origin matches the configured origin, the response includes the corresponding access-control-allow-origin value. A nonmatching origin can still receive an ordinary server response while lacking that browser permission header. The helper returns status, actual body and the allow-origin header. A host failure is plain text rather than the endpoint JSON, so the test handles each representation deliberately. There is no real DNS lookup or browser navigation; TestClient sends the synthetic headers in process.

Focused decision: use the complete example for context

python

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.example.test"],
    allow_credentials=False,
    allow_methods=["GET"],
)
app.add_middleware(
    TrustedHostMiddleware,
    allowed_hosts=["testserver", "api.example.test"],
)
Focused decision
Reconstruct this focused decision after the warm-up. The complete imports, model or application, and test client remain in the worked example. Typing records practice; only the downloadable computer lab executes Python.

Counterexamples and common mistakes

The counterexample permits every host.

The counterexample permits every host. A request using evil.example.test then reaches the route instead of being rejected. This is a host-policy regression even if the CORS allowlist remains unchanged. The two middleware checks must not be collapsed into one security claim.

CORS is not an authentication firewall: nonbrowser clients do not become authorized or unauthorized solely because of Origin. Also avoid combining unrestricted origins with credentialed cross-origin behavior as a casual default. Select a policy appropriate to the actual credential model and test preflight separately when needed.

Controlled mistake: observe the actual wrong behavior

python

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.testclient import TestClient

def inspect_example(request):
    app = FastAPI()
    app.add_middleware(CORSMiddleware, allow_origins=["https://app.example.test"],
                       allow_credentials=False, allow_methods=["GET"], allow_headers=[])
    app.add_middleware(TrustedHostMiddleware, allowed_hosts=["*"])
    @app.get("/")
    def read():
        return {"ok": True}
    with TestClient(app) as client:
        response = client.get("/", headers={"Host": request.get("host", "testserver"),
                              "Origin": request.get("origin", "https://app.example.test")})
        return {"status": response.status_code,
                "body": response.json() if response.status_code == 200 else response.text,
                "allow": response.headers.get("access-control-allow-origin")}
Reasoning
The counterexample permits every host. A request using evil.example.test then reaches the route instead of being rejected. This is a host-policy regression even if the CORS allowlist remains unchanged. The two middleware checks must not be collapsed into one security claim. CORS is not an authentication firewall: nonbrowser clients do not become authorized or unauthorized solely because of Origin. Also avoid combining unrestricted origins with credentialed cross-origin behavior as a casual default. Select a policy appropriate to the actual credential model and test preflight separately when needed.

Independent practice: one bounded change

Add preview.example.test as one additional allowed host.

Add preview.example.test as one additional allowed host. Keep evil.example.test rejected and preserve the explicit browser origin. This is a bounded preview-host configuration change, not a request to replace the allowlist with a wildcard.

Check the new host with the allowed origin, an untrusted host and an existing host with an unlisted origin. The last should retain its ordinary response but no matching allow-origin header. Do not fabricate that header in the test helper or treat its absence as server-side authentication.

Step 1: Allow one preview host while preserving host rejection and the explicit browser-origin policy. Step 2: Retain the working imports and application structure. Change the stated policy, run the supplied practice cases, then explain the newly accepted or rejected boundary.

Practice starter: solve before reading the solution

python

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.testclient import TestClient

def inspect_example(request):
    app = FastAPI()
    app.add_middleware(CORSMiddleware, allow_origins=["https://app.example.test"],
                       allow_credentials=False, allow_methods=["GET"], allow_headers=[])
    app.add_middleware(TrustedHostMiddleware, allowed_hosts=["testserver", "api.example.test"])
    @app.get("/")
    def read():
        return {"ok": True}
    with TestClient(app) as client:
        response = client.get("/", headers={"Host": request.get("host", "testserver"),
                              "Origin": request.get("origin", "https://app.example.test")})
        return {"status": response.status_code,
                "body": response.json() if response.status_code == 200 else response.text,
                "allow": response.headers.get("access-control-allow-origin")}

# Independent task: Allow one preview host while preserving host rejection and the explicit browser-origin policy.
Reasoning
Allow one preview host while preserving host rejection and the explicit browser-origin policy.
  • Hint 1: Find the one field or branch that owns this policy.
  • Hint 2: Compare the first changed acceptance case with the retained baseline.

Solution, tests and trade-offs

The solution expands only the host list.

The solution expands only the host list. The preview request succeeds and receives the same explicit CORS policy when its Origin matches. The foreign host remains rejected. The unlisted origin case demonstrates that host acceptance and browser reading permission can vary independently.

A deployed service behind proxies also needs trusted proxy configuration, HTTPS handling and appropriate cookie flags. Do not trust every forwarded header merely because the application is behind a gateway. This fixture teaches two middleware boundaries, not a complete production perimeter.

Practice solution and acceptance checks

python

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.testclient import TestClient

def inspect_example(request):
    app = FastAPI()
    app.add_middleware(CORSMiddleware, allow_origins=["https://app.example.test"],
                       allow_credentials=False, allow_methods=["GET"], allow_headers=[])
    app.add_middleware(TrustedHostMiddleware, allowed_hosts=["testserver", "api.example.test", "preview.example.test"])
    @app.get("/")
    def read():
        return {"ok": True}
    with TestClient(app) as client:
        response = client.get("/", headers={"Host": request.get("host", "testserver"),
                              "Origin": request.get("origin", "https://app.example.test")})
        return {"status": response.status_code,
                "body": response.json() if response.status_code == 200 else response.text,
                "allow": response.headers.get("access-control-allow-origin")}
Reasoning
The solution expands only the host list. The preview request succeeds and receives the same explicit CORS policy when its Origin matches. The foreign host remains rejected. The unlisted origin case demonstrates that host acceptance and browser reading permission can vary independently. A deployed service behind proxies also needs trusted proxy configuration, HTTPS handling and appropriate cookie flags. Do not trust every forwarded header merely because the application is behind a gateway. This fixture teaches two middleware boundaries, not a complete production perimeter.

Interview reasoning and next step

Explain why an unlisted Origin can still produce status 200 and why that does not mean CORS failed.

Explain why an unlisted Origin can still produce status 200 and why that does not mean CORS failed. Then identify the host-rejection observation that catches the wildcard counterexample. Use precise claims about browser behavior and server access.

The next lesson returns to vacancy data: filters must run before pagination, and counts must describe the filtered scope. A secure middleware layer cannot correct a page whose count or country selection misrepresents the underlying data.

Optional stretch — after the core task works: Add one independently reasoned boundary case and explain its expected value before running it.

Computer practice: https://applydjinn.com/typorithm/python-api-lab-20261105.zip. Use an isolated Python environment and the pinned dependencies. Run verify.py --track fastapi, then edit the working exercise baseline and run --practice fa-middleware. The reference suite rejects meaningful counterexamples and checks values, types and unchanged inputs. All data is owned and all HTTP clients are in-process. No production database, feed, account or paid service is used.

Restrict accepted hosts and expose only an explicit browser-origin policy.