Repository: litestar-org/litestar

Issue:
bug: CORS middleware not working with mounted applications
### Reported by
[__peter__](https://discord.com/users/947242363439448155) in Discord: CORS middleware not working with mounted applications

### Description
CORS pre-flight requests are passed to the mounted application, and not handled by the framework's CORS implementation.

### MCVE
```py
from http import HTTPStatus

import uvicorn
from litestar import Litestar, get, Response, asgi
from litestar.config.cors import CORSConfig


@get("/route")
async def route() -> Response[str]:
    return Response(content="Content", status_code=HTTPStatus.METHOD_NOT_ALLOWED)


cors_config = CORSConfig(allow_methods=["*"], allow_origins=["https://some-domain.com"])
app = Litestar(
    cors_config=cors_config,
    route_handlers=[
        asgi("/app", is_mount=True)(Litestar()),
        route,
    ]
)

if __name__ == "__main__":
    uvicorn.run("main:app", reload=True)
```
```py
import httpx

for path in ["/app", "/route"]:
    response = httpx.options(
        f"http://127.0.0.1:8000{path}",
        headers={"origin": "https://some-domain.com"},
    )
    print(response.status_code)
    print(response.content)
    print(response.headers)
```

### Logs
```
           /^\/^\
         _|__|  O|
\/     /~     \_/ \
 \____|__________/  \
        \_______      \
                `\     \                 \
                  |     |                  \
                 /      /                    \
                /     /                       \\
              /      /                         \ \
             /     /                            \  \
           /     /             _----_            \   \
          /     /           _-~      ~-_         |   |
         (      (        _-~    _--_    ~-_     _/   |
          \      ~-____-~    _-~    ~-_    ~-_-~    /
            ~-_           _-~          ~-_       _-~
               ~--______-~                ~-___-~
```

### Litestar Version
2.8.2

<!-- POLAR PLEDGE BADGE START -->
---
> [!NOTE]  
> While we are open for sponsoring on [GitHub Sponsors](https://github.com/sponsors/litestar-org/) and 
> [OpenCollective](https://opencollective.com/litestar), we also utilize [Polar.sh](https://polar.sh/) to engage in pledge-based sponsorship.
>
> Check out all issues funded or available for funding [on our Polar.sh dashboard](https://polar.sh/litestar-org)
> * If you would like to see an issue prioritized, make a pledge towards it!
> * We receive the pledge once the issue is completed & verified
> * This, along with engagement in the community, helps us know which features are a priority to our users.

<a href="https://polar.sh/litestar-org/litestar/issues/3402">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/litestar-org/litestar/issues/3402/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/litestar-org/litestar/issues/3402/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->



Discussion:

Resolving patch:
diff --git a/litestar/middleware/_internal.py b/litestar/middleware/_internal.py
--- a/litestar/middleware/_internal.py
+++ b/litestar/middleware/_internal.py
@@ -2,14 +2,19 @@
 
 from typing import TYPE_CHECKING
 
+from litestar.constants import DEFAULT_ALLOWED_CORS_HEADERS
 from litestar.datastructures import Headers, MutableScopeHeaders
-from litestar.enums import ScopeType
+from litestar.enums import HttpMethod, MediaType, ScopeType
 from litestar.middleware.base import AbstractMiddleware
+from litestar.response import Response
+from litestar.status_codes import HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST
 
 if TYPE_CHECKING:
     from litestar.config.cors import CORSConfig
     from litestar.types import ASGIApp, Message, Receive, Scope, Send
 
+__all__ = ("CORSMiddleware",)
+
 
 class CORSMiddleware(AbstractMiddleware):
     """CORS Middleware."""
@@ -36,7 +41,15 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
             None
         """
         headers = Headers.from_scope(scope=scope)
-        if origin := headers.get("origin"):
+        origin = headers.get("origin")
+
+        if scope["type"] == ScopeType.HTTP and scope["method"] == HttpMethod.OPTIONS and origin:
+            request = scope["app"].request_class(scope=scope, receive=receive, send=send)
+            asgi_response = self._create_preflight_response(origin=origin, request_headers=headers).to_asgi_response(
+                app=None, request=request
+            )
+            await asgi_response(scope, receive, send)
+        elif origin:
             await self.app(scope, receive, self.send_wrapper(send=send, origin=origin, has_cookie="cookie" in headers))
         else:
             await self.app(scope, receive, send)
@@ -65,15 +78,55 @@ async def wrapped_send(message: Message) -> None:
                     headers["Access-Control-Allow-Origin"] = origin
                     headers["Vary"] = "Origin"
 
-                # We don't want to overwrite this for preflight requests.
-                allow_headers = headers.get("Access-Control-Allow-Headers")
-                if not allow_headers and self.config.allow_headers:
-                    headers["Access-Control-Allow-Headers"] = ", ".join(sorted(set(self.config.allow_headers)))
+                headers["Access-Control-Allow-Headers"] = ", ".join(sorted(set(self.config.allow_headers)))
 
-                allow_methods = headers.get("Access-Control-Allow-Methods")
-                if not allow_methods and self.config.allow_methods:
-                    headers["Access-Control-Allow-Methods"] = ", ".join(sorted(set(self.config.allow_methods)))
+                headers["Access-Control-Allow-Methods"] = ", ".join(sorted(set(self.config.allow_methods)))
 
             await send(message)
 
         return wrapped_send
+
+    def _create_preflight_response(self, origin: str, request_headers: Headers) -> Response[str | None]:
+        pre_flight_method = request_headers.get("Access-Control-Request-Method")
+        failures = []
+
+        if not self.config.is_allow_all_methods and (
+            pre_flight_method and pre_flight_method not in self.config.allow_methods
+        ):
+            failures.append("method")
+
+        response_headers = self.config.preflight_headers.copy()
+
+        if not self.config.is_origin_allowed(origin):
+            failures.append("Origin")
+        elif response_headers.get("Access-Control-Allow-Origin") != "*":
+            response_headers["Access-Control-Allow-Origin"] = origin
+
+        pre_flight_requested_headers = [
+            header.strip()
+            for header in request_headers.get("Access-Control-Request-Headers", "").split(",")
+            if header.strip()
+        ]
+
+        if pre_flight_requested_headers:
+            if self.config.is_allow_all_headers:
+                response_headers["Access-Control-Allow-Headers"] = ", ".join(
+                    sorted(set(pre_flight_requested_headers) | DEFAULT_ALLOWED_CORS_HEADERS)  # pyright: ignore
+                )
+            elif any(header.lower() not in self.config.allow_headers for header in pre_flight_requested_headers):
+                failures.append("headers")
+
+        return (
+            Response(
+                content=f"Disallowed CORS {', '.join(failures)}",
+                status_code=HTTP_400_BAD_REQUEST,
+                media_type=MediaType.TEXT,
+            )
+            if failures
+            else Response(
+                content=None,
+                status_code=HTTP_204_NO_CONTENT,
+                media_type=MediaType.TEXT,
+                headers=response_headers,
+            )
+        )
diff --git a/litestar/routes/http.py b/litestar/routes/http.py
--- a/litestar/routes/http.py
+++ b/litestar/routes/http.py
@@ -5,15 +5,13 @@
 
 from msgspec.msgpack import decode as _decode_msgpack_plain
 
-from litestar.constants import DEFAULT_ALLOWED_CORS_HEADERS
-from litestar.datastructures.headers import Headers
 from litestar.datastructures.upload_file import UploadFile
 from litestar.enums import HttpMethod, MediaType, ScopeType
 from litestar.exceptions import ClientException, ImproperlyConfiguredException, SerializationException
 from litestar.handlers.http_handlers import HTTPRouteHandler
 from litestar.response import Response
 from litestar.routes.base import BaseRoute
-from litestar.status_codes import HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST
+from litestar.status_codes import HTTP_204_NO_CONTENT
 from litestar.types.empty import Empty
 from litestar.utils.scope.state import ScopeState
 
@@ -255,57 +253,6 @@ def options_handler(scope: Scope) -> Response:
             Returns:
                 Response
             """
-            cors_config = scope["app"].cors_config
-            request_headers = Headers.from_scope(scope=scope)
-            origin = request_headers.get("origin")
-
-            if cors_config and origin:
-                pre_flight_method = request_headers.get("Access-Control-Request-Method")
-                failures = []
-
-                if not cors_config.is_allow_all_methods and (
-                    pre_flight_method and pre_flight_method not in cors_config.allow_methods
-                ):
-                    failures.append("method")
-
-                response_headers = cors_config.preflight_headers.copy()
-
-                if not cors_config.is_origin_allowed(origin):
-                    failures.append("Origin")
-                elif response_headers.get("Access-Control-Allow-Origin") != "*":
-                    response_headers["Access-Control-Allow-Origin"] = origin
-
-                pre_flight_requested_headers = [
-                    header.strip()
-                    for header in request_headers.get("Access-Control-Request-Headers", "").split(",")
-                    if header.strip()
-                ]
-
-                if pre_flight_requested_headers:
-                    if cors_config.is_allow_all_headers:
-                        response_headers["Access-Control-Allow-Headers"] = ", ".join(
-                            sorted(set(pre_flight_requested_headers) | DEFAULT_ALLOWED_CORS_HEADERS)  # pyright: ignore
-                        )
-                    elif any(
-                        header.lower() not in cors_config.allow_headers for header in pre_flight_requested_headers
-                    ):
-                        failures.append("headers")
-
-                return (
-                    Response(
-                        content=f"Disallowed CORS {', '.join(failures)}",
-                        status_code=HTTP_400_BAD_REQUEST,
-                        media_type=MediaType.TEXT,
-                    )
-                    if failures
-                    else Response(
-                        content=None,
-                        status_code=HTTP_204_NO_CONTENT,
-                        media_type=MediaType.TEXT,
-                        headers=response_headers,
-                    )
-                )
-
             return Response(
                 content=None,
                 status_code=HTTP_204_NO_CONTENT,


Test patch:
diff --git a/tests/e2e/test_cors/__init__.py b/tests/e2e/test_cors/__init__.py
new file mode 100644
diff --git a/tests/e2e/test_cors/test_cors_allowed_headers.py b/tests/e2e/test_cors/test_cors_allowed_headers.py
new file mode 100644
--- /dev/null
+++ b/tests/e2e/test_cors/test_cors_allowed_headers.py
@@ -0,0 +1,49 @@
+from litestar import Litestar, get
+from litestar.config.cors import CORSConfig
+from litestar.status_codes import HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST
+from litestar.testing import TestClient
+
+
+@get("/headers-test")
+async def headers_handler() -> str:
+    return "Test Successful!"
+
+
+cors_config = CORSConfig(
+    allow_methods=["GET"],
+    allow_origins=["https://allowed-origin.com"],
+    allow_headers=["X-Custom-Header", "Content-Type"],
+)
+app = Litestar(route_handlers=[headers_handler], cors_config=cors_config)
+
+
+def test_cors_with_specific_allowed_headers() -> None:
+    with TestClient(app) as client:
+        response = client.options(
+            "/endpoint",
+            headers={
+                "Origin": "https://allowed-origin.com",
+                "Access-Control-Request-Method": "GET",
+                "Access-Control-Request-Headers": "X-Custom-Header, Content-Type",
+            },
+        )
+        assert response.status_code == HTTP_204_NO_CONTENT
+        assert "x-custom-header" in response.headers["access-control-allow-headers"]
+        assert "content-type" in response.headers["access-control-allow-headers"]
+
+
+def test_cors_with_unauthorized_headers() -> None:
+    with TestClient(app) as client:
+        response = client.options(
+            "/endpoint",
+            headers={
+                "Origin": "https://allowed-origin.com",
+                "Access-Control-Request-Method": "GET",
+                "Access-Control-Request-Headers": "X-Not-Allowed-Header",
+            },
+        )
+        assert response.status_code == HTTP_400_BAD_REQUEST
+        assert (
+            "access-control-allow-headers" not in response.headers
+            or "x-not-allowed-header" not in response.headers.get("access-control-allow-headers", "")
+        )
diff --git a/tests/e2e/test_cors/test_cors_allowed_methods.py b/tests/e2e/test_cors/test_cors_allowed_methods.py
new file mode 100644
--- /dev/null
+++ b/tests/e2e/test_cors/test_cors_allowed_methods.py
@@ -0,0 +1,39 @@
+from http import HTTPStatus
+
+from litestar import Litestar, get
+from litestar.config.cors import CORSConfig
+from litestar.testing import TestClient
+
+
+@get("/method-test")
+async def method_handler() -> str:
+    return "Method Test Successful!"
+
+
+cors_config = CORSConfig(allow_methods=["GET", "POST"], allow_origins=["https://allowed-origin.com"])
+app = Litestar(route_handlers=[method_handler], cors_config=cors_config)
+
+
+def test_cors_allowed_methods() -> None:
+    with TestClient(app) as client:
+        response = client.options(
+            "/method-test", headers={"Origin": "https://allowed-origin.com", "Access-Control-Request-Method": "GET"}
+        )
+        assert response.status_code == HTTPStatus.NO_CONTENT
+        assert response.headers["access-control-allow-origin"] == "https://allowed-origin.com"
+        assert "GET" in response.headers["access-control-allow-methods"]
+
+        response = client.options(
+            "/method-test", headers={"Origin": "https://allowed-origin.com", "Access-Control-Request-Method": "POST"}
+        )
+        assert response.status_code == HTTPStatus.NO_CONTENT
+        assert "POST" in response.headers["access-control-allow-methods"]
+
+
+def test_cors_disallowed_methods() -> None:
+    with TestClient(app) as client:
+        response = client.options(
+            "/method-test", headers={"Origin": "https://allowed-origin.com", "Access-Control-Request-Method": "PUT"}
+        )
+        assert response.status_code == HTTPStatus.BAD_REQUEST
+        assert "PUT" not in response.headers.get("access-control-allow-methods", "")
diff --git a/tests/e2e/test_cors/test_cors_credentials.py b/tests/e2e/test_cors/test_cors_credentials.py
new file mode 100644
--- /dev/null
+++ b/tests/e2e/test_cors/test_cors_credentials.py
@@ -0,0 +1,39 @@
+from litestar import Litestar, get
+from litestar.config.cors import CORSConfig
+from litestar.status_codes import HTTP_204_NO_CONTENT
+from litestar.testing import TestClient
+
+
+@get("/credentials-test")
+async def credentials_handler() -> str:
+    return "Test Successful!"
+
+
+def test_cors_with_credentials_allowed() -> None:
+    cors_config = CORSConfig(
+        allow_methods=["GET"], allow_origins=["https://allowed-origin.com"], allow_credentials=True
+    )
+    app = Litestar(route_handlers=[credentials_handler], cors_config=cors_config)
+
+    with TestClient(app) as client:
+        response = client.options(
+            "/endpoint", headers={"Origin": "https://allowed-origin.com", "Access-Control-Request-Method": "GET"}
+        )
+        assert response.status_code == HTTP_204_NO_CONTENT
+        assert response.headers["access-control-allow-credentials"] == "true"
+
+
+def test_cors_with_credentials_disallowed() -> None:
+    cors_config = CORSConfig(
+        allow_methods=["GET"],
+        allow_origins=["https://allowed-origin.com"],
+        allow_credentials=False,  # Credentials should not be allowed
+    )
+    app = Litestar(route_handlers=[credentials_handler], cors_config=cors_config)
+
+    with TestClient(app) as client:
+        response = client.options(
+            "/endpoint", headers={"Origin": "https://allowed-origin.com", "Access-Control-Request-Method": "GET"}
+        )
+        assert response.status_code == HTTP_204_NO_CONTENT
+        assert "access-control-allow-credentials" not in response.headers
diff --git a/tests/e2e/test_cors/test_cors_for_middleware_exception.py b/tests/e2e/test_cors/test_cors_for_middleware_exception.py
new file mode 100644
--- /dev/null
+++ b/tests/e2e/test_cors/test_cors_for_middleware_exception.py
@@ -0,0 +1,30 @@
+from http import HTTPStatus
+
+from litestar import Litestar, get
+from litestar.config.cors import CORSConfig
+from litestar.exceptions import HTTPException
+from litestar.middleware import AbstractMiddleware
+from litestar.testing import TestClient
+from litestar.types.asgi_types import Receive, Scope, Send
+
+
+class ExceptionMiddleware(AbstractMiddleware):
+    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
+        raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Intentional Error")
+
+
+@get("/test")
+async def handler() -> str:
+    return "Should not reach this"
+
+
+cors_config = CORSConfig(allow_methods=["GET"], allow_origins=["https://allowed-origin.com"], allow_credentials=True)
+app = Litestar(route_handlers=[handler], cors_config=cors_config, middleware=[ExceptionMiddleware])
+
+
+def test_cors_on_middleware_exception() -> None:
+    with TestClient(app) as client:
+        response = client.get("/test", headers={"Origin": "https://allowed-origin.com"})
+        assert response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
+        assert response.headers["access-control-allow-origin"] == "https://allowed-origin.com"
+        assert response.headers["access-control-allow-credentials"] == "true"
diff --git a/tests/e2e/test_cors/test_cors_for_mount.py b/tests/e2e/test_cors/test_cors_for_mount.py
new file mode 100644
--- /dev/null
+++ b/tests/e2e/test_cors/test_cors_for_mount.py
@@ -0,0 +1,100 @@
+from __future__ import annotations
+
+from http import HTTPStatus
+from unittest.mock import MagicMock
+
+import pytest
+
+from litestar import Litestar, asgi
+from litestar.config.cors import CORSConfig
+from litestar.enums import ScopeType
+from litestar.testing import TestClient
+from litestar.types.asgi_types import ASGIApp, Receive, Scope, Send
+
+
+@pytest.fixture(name="asgi_mock")
+def asgi_mock_fixture() -> MagicMock:
+    return MagicMock()
+
+
+@pytest.fixture(name="asgi_app")
+def asgi_app_fixture(asgi_mock: MagicMock) -> ASGIApp:
+    async def asgi_app(scope: Scope, receive: Receive, send: Send) -> None:
+        asgi_mock()
+
+        assert scope["type"] == ScopeType.HTTP
+
+        while True:
+            event = await receive()
+            if event["type"] == "http.request" and not event.get("more_body", False):
+                break
+
+        await send(
+            {
+                "type": "http.response.start",
+                "status": 200,
+                "headers": [
+                    (b"content-type", b"text/plain"),
+                ],
+            }
+        )
+
+        await send(
+            {
+                "type": "http.response.body",
+                "body": b"Hello, world!",
+                "more_body": False,
+            }
+        )
+
+    return asgi_app
+
+
+def test_cors_middleware_for_mount(asgi_app: ASGIApp, asgi_mock: MagicMock) -> None:
+    cors_config = CORSConfig(allow_methods=["*"], allow_origins=["https://some-domain.com"])
+    app = Litestar(
+        cors_config=cors_config,
+        route_handlers=[
+            asgi("/app", is_mount=True)(asgi_app),
+        ],
+        openapi_config=None,
+    )
+
+    with TestClient(app) as client:
+        response = client.options(
+            "http://127.0.0.1:8000/app",
+            headers={"origin": "https://some-domain.com"},
+        )
+        assert response.status_code == HTTPStatus.NO_CONTENT
+        assert response.headers["access-control-allow-origin"] == "https://some-domain.com"
+    asgi_mock.assert_not_called()
+
+
+def test_asgi_app_no_origin_header(asgi_app: ASGIApp, asgi_mock: MagicMock) -> None:
+    cors_config = CORSConfig(allow_methods=["*"], allow_origins=["https://some-domain.com"])
+    app = Litestar(
+        cors_config=cors_config,
+        route_handlers=[
+            asgi("/app", is_mount=True)(asgi_app),
+        ],
+        openapi_config=None,
+    )
+
+    with TestClient(app) as client:
+        response = client.options("http://127.0.0.1/app")
+        assert response.status_code == HTTPStatus.OK
+        assert response.headers["content-type"] == "text/plain"
+        asgi_mock.assert_called()
+
+
+def test_asgi_app_without_cors_configuration(asgi_app: ASGIApp, asgi_mock: MagicMock) -> None:
+    non_cors_app = Litestar(
+        route_handlers=[asgi("/app", is_mount=True)(asgi_app)],
+        openapi_config=None,
+    )
+
+    with TestClient(non_cors_app) as client:
+        response = client.options("http://127.0.0.1:8000/app")
+        assert response.status_code == HTTPStatus.OK
+        assert response.headers["content-type"] == "text/plain"
+        asgi_mock.assert_called()
diff --git a/tests/e2e/test_cors/test_cors_for_routing_exception.py b/tests/e2e/test_cors/test_cors_for_routing_exception.py
new file mode 100644
--- /dev/null
+++ b/tests/e2e/test_cors/test_cors_for_routing_exception.py
@@ -0,0 +1,22 @@
+from http import HTTPStatus
+
+from litestar import Litestar, get
+from litestar.config.cors import CORSConfig
+from litestar.testing import TestClient
+
+
+@get("/test")
+async def handler() -> str:
+    return "Should not reach this"
+
+
+cors_config = CORSConfig(allow_methods=["GET"], allow_origins=["https://allowed-origin.com"], allow_credentials=True)
+app = Litestar(route_handlers=[handler], cors_config=cors_config)
+
+
+def test_cors_on_middleware_exception_with_origin_header() -> None:
+    with TestClient(app) as client:
+        response = client.get("/testing", headers={"Origin": "https://allowed-origin.com"})
+        assert response.status_code == HTTPStatus.NOT_FOUND
+        assert response.headers["access-control-allow-origin"] == "https://allowed-origin.com"
+        assert response.headers["access-control-allow-credentials"] == "true"
diff --git a/tests/e2e/test_cors/test_cors_origins.py b/tests/e2e/test_cors/test_cors_origins.py
new file mode 100644
--- /dev/null
+++ b/tests/e2e/test_cors/test_cors_origins.py
@@ -0,0 +1,42 @@
+from http import HTTPStatus
+
+from litestar import Litestar, get
+from litestar.config.cors import CORSConfig
+from litestar.testing import TestClient
+
+
+@get("/endpoint")
+async def handler() -> str:
+    return "Hello, world!"
+
+
+cors_config = CORSConfig(
+    allow_methods=["GET"], allow_origins=["https://allowed-origin.com", "https://another-allowed-origin.com"]
+)
+app = Litestar(route_handlers=[handler], cors_config=cors_config)
+
+
+def test_cors_with_allowed_origins() -> None:
+    with TestClient(app) as client:
+        response = client.options(
+            "/custom-options", headers={"Origin": "https://allowed-origin.com", "Access-Control-Request-Method": "GET"}
+        )
+        assert response.status_code == HTTPStatus.NO_CONTENT
+        assert response.headers["access-control-allow-origin"] == "https://allowed-origin.com"
+
+        response = client.options(
+            "/custom-options",
+            headers={"Origin": "https://another-allowed-origin.com", "Access-Control-Request-Method": "GET"},
+        )
+        assert response.status_code == HTTPStatus.NO_CONTENT
+        assert response.headers["access-control-allow-origin"] == "https://another-allowed-origin.com"
+
+
+def test_cors_with_disallowed_origin() -> None:
+    with TestClient(app) as client:
+        response = client.options(
+            "/custom-options",
+            headers={"Origin": "https://disallowed-origin.com", "Access-Control-Request-Method": "GET"},
+        )
+        assert response.status_code == HTTPStatus.BAD_REQUEST
+        assert "access-control-allow-origin" not in response.headers
diff --git a/tests/e2e/test_cors/test_custom_options_handlers.py b/tests/e2e/test_cors/test_custom_options_handlers.py
new file mode 100644
--- /dev/null
+++ b/tests/e2e/test_cors/test_custom_options_handlers.py
@@ -0,0 +1,38 @@
+from http import HTTPStatus
+
+from litestar import Litestar, route
+from litestar.config.cors import CORSConfig
+from litestar.enums import HttpMethod
+from litestar.response import Response
+from litestar.testing import TestClient
+
+
+@route("/custom-options", http_method=HttpMethod.OPTIONS)
+async def custom_options_handler() -> Response[str]:
+    return Response(
+        status_code=200,
+        headers={"Custom-Handler": "Active"},
+        content="Handled by Custom Options",
+    )
+
+
+cors_config = CORSConfig(allow_methods=["GET", "OPTIONS"], allow_origins=["https://allowed-origin.com"])
+app = Litestar(route_handlers=[custom_options_handler], cors_config=cors_config)
+
+
+def test_custom_options_handler_cors_pre_flight_request() -> None:
+    with TestClient(app) as client:
+        response = client.options(
+            "/custom-options", headers={"Origin": "https://allowed-origin.com", "Access-Control-Request-Method": "GET"}
+        )
+        assert response.status_code == HTTPStatus.NO_CONTENT
+        assert "access-control-allow-origin" in response.headers
+        assert "Custom-Handler" not in response.headers
+
+
+def test_custom_options_handler_non_cors_request() -> None:
+    with TestClient(app) as client:
+        response = client.options("/custom-options")
+        assert response.status_code == 200
+        assert response.headers.get("Custom-Handler") == "Active"
+        assert response.text == "Handled by Custom Options"
diff --git a/tests/e2e/test_cors/test_non_cors_options.py b/tests/e2e/test_cors/test_non_cors_options.py
new file mode 100644
--- /dev/null
+++ b/tests/e2e/test_cors/test_non_cors_options.py
@@ -0,0 +1,33 @@
+from litestar import Litestar, get
+from litestar.config.cors import CORSConfig
+from litestar.status_codes import HTTP_204_NO_CONTENT
+from litestar.testing import TestClient
+
+
+@get("/handler")
+async def handler() -> str:
+    return "Handler"
+
+
+def test_non_cors_options_request_no_origin_header() -> None:
+    cors_config = CORSConfig(
+        allow_methods=["PUT"],
+        allow_origins=["https://specific-domain.com"],
+    )
+    app = Litestar(route_handlers=[handler], cors_config=cors_config)
+
+    with TestClient(app) as client:
+        # Request without an 'Origin' header
+        response = client.options("/handler")
+        assert response.status_code == HTTP_204_NO_CONTENT
+        assert response.headers["allow"] == "GET, OPTIONS"
+
+
+def test_non_cors_options_no_config() -> None:
+    app = Litestar(route_handlers=[handler])
+
+    with TestClient(app) as client:
+        # Request with an origin that does not require CORS handling
+        response = client.options("/handler", headers={"Origin": "https://not-configured-origin.com"})
+        assert response.status_code == HTTP_204_NO_CONTENT
+        assert response.headers["allow"] == "GET, OPTIONS"