From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:34:30 +0000 (+0100) Subject: 🐛 Fix security schemes in OpenAPI when added at the top level app (#14266) X-Git-Tag: 0.120.4~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=496de1816aa01a59c75a7dcd27fd3c6245fa255a;p=thirdparty%2Ffastapi%2Ffastapi.git 🐛 Fix security schemes in OpenAPI when added at the top level app (#14266) Co-authored-by: Sebastián Ramírez --- diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index d2d4e8b4c..6477a2cba 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -248,6 +248,14 @@ def get_dependant( path_param_names = get_path_param_names(path) endpoint_signature = get_typed_signature(call) signature_params = endpoint_signature.parameters + if isinstance(call, SecurityBase): + use_scopes: List[str] = [] + if isinstance(call, (OAuth2, OpenIdConnect)): + use_scopes = security_scopes + security_requirement = SecurityRequirement( + security_scheme=call, scopes=use_scopes + ) + dependant.security_requirements.append(security_requirement) for param_name, param in signature_params.items(): is_path_param = param_name in path_param_names param_details = analyze_param( @@ -269,16 +277,6 @@ def get_dependant( security_scopes=use_security_scopes, use_cache=param_details.depends.use_cache, ) - if isinstance(param_details.depends.dependency, SecurityBase): - use_scopes: List[str] = [] - if isinstance( - param_details.depends.dependency, (OAuth2, OpenIdConnect) - ): - use_scopes = use_security_scopes - security_requirement = SecurityRequirement( - security_scheme=param_details.depends.dependency, scopes=use_scopes - ) - sub_dependant.security_requirements.append(security_requirement) dependant.dependencies.append(sub_dependant) continue if add_non_field_param_to_dependency( diff --git a/tests/test_top_level_security_scheme_in_openapi.py b/tests/test_top_level_security_scheme_in_openapi.py new file mode 100644 index 000000000..e2de31af5 --- /dev/null +++ b/tests/test_top_level_security_scheme_in_openapi.py @@ -0,0 +1,60 @@ +# Test security scheme at the top level, including OpenAPI +# Ref: https://github.com/fastapi/fastapi/discussions/14263 +# Ref: https://github.com/fastapi/fastapi/issues/14271 +from fastapi import Depends, FastAPI +from fastapi.security import HTTPBearer +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +app = FastAPI() + +bearer_scheme = HTTPBearer() + + +@app.get("/", dependencies=[Depends(bearer_scheme)]) +async def get_root(): + return {"message": "Hello, World!"} + + +client = TestClient(app) + + +def test_get_root(): + response = client.get("/", headers={"Authorization": "Bearer token"}) + assert response.status_code == 200, response.text + assert response.json() == {"message": "Hello, World!"} + + +def test_get_root_no_token(): + response = client.get("/") + assert response.status_code == 403, response.text + assert response.json() == {"detail": "Not authenticated"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Get Root", + "operationId": "get_root__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "security": [{"HTTPBearer": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}} + }, + } + )