Skip to content

Bug: routes and routers cannot override OpenAPI security requirement #4016

@tofran

Description

@tofran

Description

Context

Litestar provides a way to declare global security components, for example by the using of a global security configuration like JWTAuth or by building your own (Example implementation in AbstractSecurityConfig.on_app_init).

This creates both a global security component and requirement in your openapi:

Image

According to the openapi v3 security documentation you can override at the security keyword at the operation level:

When used on the root level, security applies the specified security schemes globally to all API operations, unless overridden on the operation level.

The problem

The problem is that you cannot override the security requirement neither at the route or router level with an empty list.

@get(
    "/login",
    # security=[],  # This should override the global security, but is being ignored
)
async def login() -> str:
    return "Sample"

Although Litestar provides an API to do so, it does not respect the provided in the security because of how it internally handles empty security requirements.

Note that passing a non empty list like security=[{"SAMPLE": []}] would actually made it to the openapi security requirements of the route.

Technical details of the bug and proposed fix

The bug is effectively here:

security=route_handler.resolve_security() or None,

The or None replaces any empty resolved security requirement with None, removing it.

The problem is that simply removing or None would make all routes have an overridden security keyword, which is clearly not the desirable effect.

My proposal is to change the default from something which represents as it being not set and handle it properly in the resolve_security method.

Should I attempt a fix?

MCVE

`app.py`
from typing import Any

from litestar.security.jwt.auth import JWTAuth

from litestar import Litestar, get
from litestar.connection import ASGIConnection


@get(
    "/login",
    security=[],  # This should override the global security, but is being ignored
)
async def login() -> str:
    return "Sample"


@get("/hello-world")
async def hello() -> str:
    return "Hello world"


async def retrieve_user_handler(
    session: dict[str, Any],
    connection: ASGIConnection[Any, Any, Any, Any],
) -> str:
    return "ignored"


jwt_auth = JWTAuth(
    token_secret="secret",
    retrieve_user_handler=retrieve_user_handler,
    exclude=[
        "/login",
        "/schema",
    ],
)


app = Litestar(
    route_handlers=[login, hello],
    on_app_init=[jwt_auth.on_app_init],
)

# How to run this:
# Instal dependencies with pip install litestar[full] uvicorn
# Start the server with `python -m uvicorn app:app --port 8000 --reload``

Steps to reproduce

1. Run the above code.
2. See that the route `/login` has the security in the swagger UI `http://localhost:8000/schema/swagger/` instead of having been overridden by the `security` route argument at line `11`.

Screenshots

Expected result:

Image

Actual:

Image

Litestar Version

2.14.0

Platform

  • Linux
  • Mac
  • Windows
  • Other (Please specify in the description above)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Bug 🐛This is something that is not working as expected

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions