Secure FastAPI Code Generated by AI
Separate Pydantic request validation from object authorization, then configure CORS, hosts, proxy trust, TLS, errors, and API documentation.

On this page
- Separate four decisions in each endpoint
- The risky pattern: a valid model and an unscoped lookup
- Scope object queries in the service layer
- Configure CORS as an explicit browser contract
- Restrict hosts and proxy headers from real topology
- Bound requests, responses, and failures
- Decide how API documentation is exposed
- Keep background tasks and WebSockets inside the policy
- Test through the production-shaped ASGI path
- Sources
Direct answer: Secure generated FastAPI code by treating Pydantic as request-shape validation, not authorization. Verify identity and tenant access in dependencies or service code, query objects within that scope, list credentialed CORS origins explicitly, restrict accepted hosts, trust forwarded headers only from known proxies, keep secrets outside source, and test direct cross-account requests behind the production-shaped TLS proxy.
FastAPI, Pydantic, Starlette, Uvicorn, CORS, and proxy guidance was reviewed against official documentation on July 17, 2026. Verify the versions together because FastAPI inherits behavior from its underlying ASGI stack.
FastAPI makes typed API code concise. That can produce false confidence in AI-generated projects: an endpoint has an OAuth2 dependency, a Pydantic model, and automatic documentation, so it looks complete. Yet the database query may still fetch any document by ID. CORS may allow more browser origins than intended. Uvicorn may trust forwarding headers from an untrusted network path.
The vibe coding security guide separates validation from evidence. A 200 for the owner and a 404 for a second tenant on the deployed route tell you more about object authorization than a complete OpenAPI schema.
Separate four decisions in each endpoint
For every route, identify:
- Shape validation: Does Pydantic accept the path, query, headers, and body?
- Authentication: Which verified principal made the request?
- Authorization: May that principal perform this action on this object now?
- Deployment trust: Which proxy supplied the host, scheme, and client address?
Do not merge these into a single get_current_user dependency. Authentication can be reusable while object authorization depends on the resource, tenant, role, state, and action.
The risky pattern: a valid model and an unscoped lookup
Vulnerable pattern. The route authenticates a user but loads the document globally.
class DocumentUpdate(BaseModel):
title: str = Field(min_length=1, max_length=120)
@app.patch("/documents/{document_id}")
def update_document(
document_id: UUID,
body: DocumentUpdate,
user: User = Depends(get_current_user),
session: Session = Depends(get_session),
):
document = session.get(Document, document_id)
if document is None:
raise HTTPException(status_code=404)
document.title = body.title
session.commit()
return document
The UUID and title are well formed, and the caller is authenticated. Neither fact establishes that the document belongs to the caller’s tenant. Account B can submit Account A’s test ID directly.
Generated code often misses this because typing is visible and immediate. The endpoint rejects malformed data, the Swagger UI shows a lock icon, and the owner flow works. Cross-tenant ownership is a business relationship outside the request model unless the developer specifies and tests it.
Scope object queries in the service layer
Keep the authenticated principal small, load current membership on the server, and include the tenant boundary in the query.
from pydantic import BaseModel, ConfigDict
class DocumentSummary(BaseModel):
id: UUID
title: str
model_config = ConfigDict(from_attributes=True)
def load_editable_document(
*,
session: Session,
document_id: UUID,
principal: Principal,
) -> Document:
statement = select(Document).where(
Document.id == document_id,
Document.tenant_id == principal.tenant_id,
Document.archived_at.is_(None),
)
document = session.scalar(statement)
if document is None:
raise HTTPException(status_code=404, detail="Not found")
return document
@app.patch("/documents/{document_id}", response_model=DocumentSummary)
def update_document(
document_id: UUID,
body: DocumentUpdate,
principal: Principal = Depends(require_editor),
session: Session = Depends(get_session),
):
document = load_editable_document(
session=session,
document_id=document_id,
principal=principal,
)
document.title = body.title
session.commit()
return DocumentSummary.model_validate(document)
from_attributes=True allows Pydantic to validate the selected ORM object by reading its attributes. Without that configuration, DocumentSummary.model_validate(document) can fail even though the response fields are present on the model. The response schema still narrows only serialization; it does not repair an unscoped query.
This example covers one active-tenant edit. It still needs decisions about shared documents, administrators, ownership transfer, concurrent updates, audit events, rollback, and which fields an editor may change.
The IDOR guide for AI-built apps provides a deeper two-account test.
Configure CORS as an explicit browser contract
CORS tells browsers which origins may read responses. It does not stop scripts, servers, or command-line clients from calling the API. Authentication and authorization must succeed even when no Origin header is present.
When cookies or authorization credentials are allowed, current FastAPI and Starlette guidance requires explicit origins, methods, and headers rather than wildcard lists.
app.add_middleware(
CORSMiddleware,
allow_origins=settings.browser_origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PATCH", "DELETE"],
allow_headers=["Authorization", "Content-Type", "X-CSRF-Token"],
)
Validate settings.browser_origins as complete origins with scheme and port. Do not use substring matching or an unrestricted regular expression. Include only deployed frontends that need browser access. Test an allowed origin, a deceptive suffix, null, a missing origin, and a preflight for every credentialed method.
The CORS guide for vibe-coded apps covers browser and server boundaries in more depth.
Restrict hosts and proxy headers from real topology
Starlette’s TrustedHostMiddleware rejects unexpected Host headers. Configure exact application hosts and deliberate wildcard subdomains if required. Decide whether its default www redirect matches the product.
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=settings.allowed_hosts,
www_redirect=False,
)
TLS commonly terminates before Uvicorn. FastAPI’s proxy documentation explains that forwarded scheme and client data should be trusted only from known proxies. Configure --forwarded-allow-ips or the equivalent deployment option from the actual ingress addresses. Do not publish a universal value such as * unless the application port is provably reachable only through a trusted local proxy.
Draw every path from the internet, health check, internal service, and maintenance network to Uvicorn. Send forged X-Forwarded-For, X-Forwarded-Proto, and Host headers through each path. Check generated redirects, secure cookies, absolute URLs, rate limits, and logs.
HTTPSRedirectMiddleware can redirect insecure schemes, but it depends on correct scheme detection behind the proxy. The ingress should also enforce HTTPS. A redirect does not encrypt traffic that already crossed an untrusted hop.
Bound requests, responses, and failures
Pydantic constraints should cover collection sizes, string lengths, enums, and unknown-field policy. The reverse proxy and ASGI stack should also bound body size, header size, connections, and timeouts. Avoid reading an unlimited upload into memory.
Use explicit response models or DTOs. Do not return ORM objects with private columns, tokens, internal notes, or unrelated tenant data. Map known validation, authorization, conflict, and provider failures to stable responses. Keep debug tracebacks out of production responses and scrub secrets from logs.
Rate-limit login, reset, export, search, and expensive model-backed endpoints according to their abuse and cost. Confirm the client-address source before keying a limit by IP. Handle downstream timeouts and cancellations so a disconnected request does not leave uncontrolled work running.
Decide how API documentation is exposed
FastAPI can serve OpenAPI, Swagger UI, and ReDoc automatically. Documentation is valuable, and a public API may intentionally publish it. Do not treat obscurity as endpoint security.
For a private service, disable public documentation with application settings or expose it through a separately authenticated administrative path. Ensure the OpenAPI route, docs assets, and schema JSON receive the intended control. Even when docs are disabled, every endpoint still needs authorization and negative tests.
Keep secrets in the deployment secret store or managed runtime identity, not Python source, sample settings, images, or committed .env files. Separate test, preview, and production credentials.
The browser-local AI app security checklist can structure the review. It cannot inspect dependencies, proxy topology, runtime settings, Pydantic semantics, or deployed object authorization.
Keep background tasks and WebSockets inside the policy
FastAPI background tasks run after the response, so the route must authorize the job before scheduling it. Pass a minimal immutable identifier rather than a live ORM object, raw request, access token, or user-controlled role. For work that must survive process restarts, use a durable queue and have the worker reload current tenant state before a sensitive side effect. Decide how duplicate delivery, cancellation, and failure are recorded.
WebSockets need a separate handshake and message policy. Authenticate the connection, validate its Origin when browser clients are expected, authorize every subscribed tenant or channel, bound message size and rate, and close access after membership removal. A protected HTTP route does not automatically protect a nearby WebSocket path.
Test through the production-shaped ASGI path
Use a local or authorized test environment with fake tenants.
- Call each sensitive endpoint without a token, with an invalid token, and with an expired token.
- As Account B, send Account A’s document, file, job, and export IDs.
- Test role downgrade, membership removal, archived objects, and concurrent updates.
- Send wrong types, unknown fields, oversized arrays, long strings, duplicate requests, and unsupported content types.
- Exercise allowed and deceptive CORS origins plus preflight requests.
- Forge Host and forwarding headers through each ingress path.
- Verify docs, errors, logs, cookies, and serialized responses reveal no private data.
A passing suite supports the named endpoints, accounts, ASGI versions, and proxy configuration. It does not prove all dependencies, middleware, business rules, or future deployments are safe.
Sources
Frequently asked
Does Pydantic validation authorize a FastAPI request?
No. Pydantic can validate a request's structure and constraints. Application code must still verify the principal and decide whether that principal may access the requested object or action.
Can credentialed FastAPI CORS use wildcard origins?
No. Current FastAPI and Starlette guidance requires explicit origins, methods, and headers when credentials are allowed. CORS is a browser response policy and does not replace API authentication or authorization.
Should FastAPI trust all forwarded headers behind a proxy?
No. Configure Uvicorn or the deployment layer to trust forwarded headers only from known proxy addresses, and verify the actual path. Client-supplied forwarding headers must not define the scheme, host, or client address.
Should FastAPI OpenAPI and Swagger UI be public?
Treat the schema as public unless access is enforced. Public documentation can be appropriate for a public API, but it is not a secret-control mechanism. Private APIs may disable or separately protect documentation while keeping every endpoint authorized.
Related posts
- AI App Pre-Launch Security Checklist
Run an evidence-based pre-launch review across authorization, secrets, inputs, dependencies, deployment, agents, monitoring, recovery, and retests.
- Secure an AI SaaS With Stripe
Secure Stripe billing with server-owned entitlements, raw-body webhook verification, idempotent processing, constrained keys, and failure-path tests.
- Secure a Bolt App Before Launch
Identify the Bolt backend, protect server functions and webhooks, test database policy, and verify the published deployment.