Repository: litestar-org/litestar

Issue:
Bug: OPTIONS getting 401 Error with custom AuthMiddleware
### Description

After version 2.0.0b4 POST calls fail on OPTIONS preflight with Authorization Error. I'm using a custom JWTAuthMiddleware. Code below.

### URL to code causing the issue

_No response_

### MCVE

```python
from litestar import Litestar, get
from litestar.connection import ASGIConnection
from litestar.exceptions import NotAuthorizedException
from litestar.middleware import AbstractAuthenticationMiddleware, AuthenticationResult
from litestar.middleware.base import DefineMiddleware
from litestar.testing.client import TestClient


@get("/healthcheck")
async def healthcheck() -> dict[str, str]:
    return {"status": "OK"}


class JWTAuthMiddleware(AbstractAuthenticationMiddleware):
    async def authenticate_request(self, connection: ASGIConnection) -> AuthenticationResult:
        if auth_header := connection.headers.get("authorization"):
            auth_scheme, token = auth_header.split()
            return AuthenticationResult(user=None, auth=token)
        raise NotAuthorizedException()


app = Litestar(route_handlers=[healthcheck], middleware=[DefineMiddleware(JWTAuthMiddleware)])

if __name__ == "__main__":
    client = TestClient(app)
    result = client.options("/healthcheck/")
    assert result.status_code == 204
```


### Steps to reproduce

```bash
run file with newest version of litestart (2.0.1)
```


### Screenshots

_No response_

### Logs

_No response_

### Litestar Version

2.0.0rc – 2.0.1

### Platform

- [X] Linux
- [ ] Mac
- [ ] Windows
- [ ] Other (Please specify in the description above)

<!-- POLAR PLEDGE BADGE START -->
---

## Funding
* If you would like to see an issue prioritized, make a pledge towards it!
* We receive the pledge once the issue is completed & verified

<a href="https://polar.sh/litestar-org/litestar/issues/2205">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/litestar-org/litestar/issues/2205/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/litestar-org/litestar/issues/2205/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->

StaticFilesConfig and virtual directories
I'm trying to write a ``FileSystemProtocol`` to load files from the package data using [importlib_resources](https://importlib-resources.readthedocs.io/en/latest/using.html#). But because ``directories`` is defined as ``DirectoryPath``, pydantic checks if the given directories exist in the local filesystem. 

This is not generally true, especially in any kind of virtual filesystem (e.g. a zipped package). I think this condition should be relaxed to support virtual filesystems.

https://github.com/starlite-api/starlite/blob/9bb6dcd57c10a591377cf8e3a537e9292566d5b9/starlite/config/static_files.py#L32


Discussion:

I agree. Do you want to submit a PR?

Resolving patch:
diff --git a/litestar/middleware/authentication.py b/litestar/middleware/authentication.py
--- a/litestar/middleware/authentication.py
+++ b/litestar/middleware/authentication.py
@@ -5,7 +5,7 @@
 from typing import TYPE_CHECKING, Any, Sequence
 
 from litestar.connection import ASGIConnection
-from litestar.enums import ScopeType
+from litestar.enums import HttpMethod, ScopeType
 from litestar.middleware._utils import (
     build_exclude_path_pattern,
     should_bypass_middleware,
@@ -62,7 +62,7 @@ def __init__(
         """
         self.app = app
         self.exclude = build_exclude_path_pattern(exclude=exclude)
-        self.exclude_http_methods = exclude_http_methods
+        self.exclude_http_methods = (HttpMethod.OPTIONS,) if exclude_http_methods is None else exclude_http_methods
         self.exclude_opt_key = exclude_from_auth_key
         self.scopes = scopes or {ScopeType.HTTP, ScopeType.WEBSOCKET}
 
diff --git a/litestar/types/asgi_types.py b/litestar/types/asgi_types.py
--- a/litestar/types/asgi_types.py
+++ b/litestar/types/asgi_types.py
@@ -44,6 +44,8 @@
     Union,
 )
 
+from litestar.enums import HttpMethod
+
 __all__ = (
     "ASGIApp",
     "ASGIVersion",
@@ -101,7 +103,7 @@
     from .internal_types import LitestarType, RouteHandlerType
     from .serialization import DataContainerType
 
-Method: TypeAlias = Literal["GET", "POST", "DELETE", "PATCH", "PUT", "HEAD", "TRACE", "OPTIONS"]
+Method: TypeAlias = Union[Literal["GET", "POST", "DELETE", "PATCH", "PUT", "HEAD", "TRACE", "OPTIONS"], HttpMethod]
 ScopeSession: TypeAlias = "EmptyType | Dict[str, Any] | DataContainerType | None"
 
 


Test patch:
diff --git a/tests/unit/test_middleware/test_base_authentication_middleware.py b/tests/unit/test_middleware/test_base_authentication_middleware.py
--- a/tests/unit/test_middleware/test_base_authentication_middleware.py
+++ b/tests/unit/test_middleware/test_base_authentication_middleware.py
@@ -5,6 +5,7 @@
 
 from litestar import Litestar, get, websocket
 from litestar.connection import Request, WebSocket
+from litestar.enums import HttpMethod
 from litestar.exceptions import PermissionDeniedException, WebSocketDisconnect
 from litestar.middleware.authentication import (
     AbstractAuthenticationMiddleware,
@@ -225,3 +226,34 @@ def east_handler() -> None:
 
         response = client.get("/west")
         assert response.status_code == HTTP_403_FORBIDDEN
+
+
+def test_authentication_exclude_http_methods() -> None:
+    auth_mw = DefineMiddleware(AuthMiddleware, exclude_http_methods=[HttpMethod.GET])
+
+    @get("/")
+    def exclude_get_handler() -> None:
+        return None
+
+    with create_test_client(route_handlers=[exclude_get_handler], middleware=[auth_mw]) as client:
+        response = client.get("/")
+        assert response.status_code == HTTP_200_OK
+
+        response = client.options("/")
+        assert response.status_code == HTTP_403_FORBIDDEN
+
+
+def test_authentication_exclude_http_methods_default() -> None:
+    auth_mw = DefineMiddleware(AuthMiddleware)
+
+    @get("/")
+    def exclude_get_handler() -> None:
+        return None
+
+    with create_test_client(route_handlers=[exclude_get_handler], middleware=[auth_mw]) as client:
+        response = client.get("/")
+        assert response.status_code == HTTP_403_FORBIDDEN
+
+        # OPTIONS should be excluded by default
+        response = client.options("/")
+        assert response.is_success