Skip to content
This repository was archived by the owner on Jun 25, 2025. It is now read-only.

Commit 655e407

Browse files
yannicschroeerYannic Schröer
andauthored
Excluded URLs (#6)
* excluded urls for auth middleware possible * added additional tests Co-authored-by: Yannic Schröer <yannicschroer@Yannics-MBP.fritz.box>
1 parent c203b3e commit 655e407

2 files changed

Lines changed: 26 additions & 6 deletions

File tree

fastapi_auth_middleware/middleware.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@ def identity(self) -> str:
4646
class FastAPIAuthBackend(AuthenticationBackend):
4747
""" Auth Backend for FastAPI """
4848

49-
def __init__(self, verify_header: Callable[[Dict], Tuple[List[str], BaseUser]]):
49+
def __init__(self, verify_header: Callable[[Dict], Tuple[List[str], BaseUser]], excluded_urls: List[str] = None):
5050
""" Auth Backend constructor. Part of an AuthenticationMiddleware as backend.
5151
5252
Args:
5353
verify_header (callable): A function handle that returns a list of scopes and a BaseUser
54+
excluded_urls (List[str]): A list of URL paths (e.g. ['/login', '/contact']) the middleware should not check for user credentials ( == public routes)
5455
"""
5556
self.verify_header = verify_header
57+
self.excluded_urls = [] if excluded_urls is None else excluded_urls
5658

5759
async def authenticate(self, conn: HTTPConnection) -> Tuple[AuthCredentials, BaseUser]:
5860
""" The 'magic' happens here. The authenticate method is invoked each time a route is called that the middleware is applied to.
@@ -63,6 +65,9 @@ async def authenticate(self, conn: HTTPConnection) -> Tuple[AuthCredentials, Bas
6365
Returns:
6466
Tuple[AuthCredentials, BaseUser]: A tuple of AuthCredentials (scopes) and a user object that is or inherits from BaseUser
6567
"""
68+
if conn.url.path in self.excluded_urls:
69+
return AuthCredentials(scopes=[]), "Unauthenticated User"
70+
6671
if "Authorization" not in conn.headers:
6772
raise AuthenticationError("Authorization header missing")
6873

@@ -82,7 +87,8 @@ async def authenticate(self, conn: HTTPConnection) -> Tuple[AuthCredentials, Bas
8287
def AuthMiddleware(
8388
app: FastAPI,
8489
verify_header: Callable[[str], Tuple[List[str], BaseUser]],
85-
auth_error_handler: Callable[[Request, AuthenticationError], JSONResponse] = None
90+
auth_error_handler: Callable[[Request, AuthenticationError], JSONResponse] = None,
91+
excluded_urls: List[str] = None
8692
):
8793
""" Factory method, returning an AuthenticationMiddleware
8894
Intentionally not named with lower snake case convention as this is a factory method returning a class. Should feel like a class.
@@ -91,6 +97,7 @@ def AuthMiddleware(
9197
app (FastAPI): The FastAPI instance the middleware should be applied to. The `add_middleware` function of FastAPI adds the app as first argument by default.
9298
verify_header (Callable[[str], Tuple[List[str], BaseUser]]): A function handle that returns a list of scopes and a BaseUser
9399
auth_error_handler (Callable[[Request, Exception], JSONResponse]): Optional error handler for creating responses when an exception was raised in verify_authorization_header
100+
excluded_urls (List[str]): A list of URL paths (e.g. ['/login', '/contact']) the middleware should not check for user credentials ( == public routes)
94101
95102
Examples:
96103
```python
@@ -103,4 +110,4 @@ def verify_authorization_header(auth_header: str) -> Tuple[List[str], FastAPIUse
103110
app.add_middleware(AuthMiddleware, verify_authorization_header=verify_authorization_header)
104111
```
105112
"""
106-
return AuthenticationMiddleware(app, backend=FastAPIAuthBackend(verify_header), on_error=auth_error_handler)
113+
return AuthenticationMiddleware(app, backend=FastAPIAuthBackend(verify_header=verify_header, excluded_urls=excluded_urls), on_error=auth_error_handler)

tests/test_middleware.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@ def raise_exception_in_verify_authorization_header(_):
2828

2929

3030
# Sample app with simple routes, takes a verify_authorization_header callable that is applied to the middleware
31-
def fastapi_app(verify_header: Callable, auth_error_handler: Callable = None):
31+
def fastapi_app(verify_header: Callable, auth_error_handler: Callable = None, excluded_urls: List[str] = None):
3232
app = FastAPI()
33-
app.add_middleware(AuthMiddleware, verify_header=verify_header, auth_error_handler=auth_error_handler)
33+
app.add_middleware(AuthMiddleware, verify_header=verify_header, auth_error_handler=auth_error_handler, excluded_urls=excluded_urls)
34+
35+
@app.get("/public")
36+
def public():
37+
return 'Hello Public World'
3438

3539
@app.get("/")
3640
def home():
@@ -57,7 +61,7 @@ class TestBasicBehaviour:
5761

5862
@fixture
5963
def client(self) -> TestClient:
60-
app = fastapi_app(verify_header)
64+
app = fastapi_app(verify_header, excluded_urls=["/public"])
6165
return TestClient(app)
6266

6367
@fixture
@@ -97,3 +101,12 @@ def handle_auth_error(request: Request, exception: AuthenticationError):
97101

98102
response = client_with_auth_error.get('/', headers={"Authorization": "ey.."})
99103
assert response.status_code == 401
104+
105+
def test_public_path(self, client):
106+
assert client.get("/public").status_code == 200
107+
108+
def test_public_path_with_fragments(self, client):
109+
assert client.get("/public#abcdef").status_code == 200
110+
111+
def test_public_path_with_query(self, client):
112+
assert client.get("/public?abcdef=x").status_code == 200

0 commit comments

Comments
 (0)