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

Commit 56ef630

Browse files
yannicschroeerYannic Schröer
andauthored
Improved the reusability of the middleware by passing all headers (#3)
* Improved the reusability of the middleware by passing all headers instead of Authorization * fix tests * fix signatures Co-authored-by: Yannic Schröer <yannicschroer@Yannics-MBP.fritz.box>
1 parent c1af792 commit 56ef630

7 files changed

Lines changed: 33 additions & 34 deletions

File tree

docs/docs/examples/multiple.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ elegant solution to this. But the current solution is to mount multiple apps ins
77
**multiple.py**
88

99
```python
10-
from typing import Tuple, List
10+
from typing import Tuple, List, Dict
1111
import uvicorn
1212
from fastapi import FastAPI
1313
from starlette.requests import Request
@@ -16,14 +16,14 @@ from fastapi_auth_middleware import AuthMiddleware, FastAPIUser
1616

1717

1818
# The method you have to provide
19-
def verify_authorization_header(auth_header: str) -> Tuple[List[str], FastAPIUser]:
19+
def verify_header(headers: Dict) -> Tuple[List[str], FastAPIUser]:
2020
user = FastAPIUser(first_name="Code", last_name="Specialist", user_id=1) # Usually you would decode the JWT here and verify its signature to extract the 'sub'
2121
scopes = ["admin"] # You could for instance use the scopes provided in the JWT or request them by looking up the scopes with the 'sub' somewhere
2222
return scopes, user
2323

2424

2525
users_app = FastAPI()
26-
users_app.add_middleware(AuthMiddleware, verify_authorization_header=verify_authorization_header) # Add the middleware with your verification method to the whole application
26+
users_app.add_middleware(AuthMiddleware, verify_header=verify_header) # Add the middleware with your verification method to the whole application
2727

2828

2929
@users_app.get('/') # Sample endpoint (secured)

docs/docs/examples/simple_with_scopes.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Basic Example with Scopes
22

33
```python
4-
from typing import Tuple, List
4+
from typing import Tuple, List, Dict
55
import uvicorn
66
from fastapi import FastAPI
77
from starlette.authentication import requires
@@ -11,14 +11,14 @@ from fastapi_auth_middleware import AuthMiddleware, FastAPIUser
1111

1212

1313
# The method you have to provide
14-
def verify_authorization_header(auth_header: str) -> Tuple[List[str], FastAPIUser]:
14+
def verify_header(headers: Dict) -> Tuple[List[str], FastAPIUser]:
1515
user = FastAPIUser(first_name="Code", last_name="Specialist", user_id=1) # Usually you would decode the JWT here and verify its signature to extract the 'sub'
1616
scopes = ["admin"] # You could for instance use the scopes provided in the JWT or request them by looking up the scopes with the 'sub' somewhere
1717
return scopes, user
1818

1919

2020
app = FastAPI()
21-
app.add_middleware(AuthMiddleware, verify_authorization_header=verify_authorization_header) # Add the middleware with your verification method to the whole application
21+
app.add_middleware(AuthMiddleware, verify_header=verify_header) # Add the middleware with your verification method to the whole application
2222

2323

2424
@app.get('/home') # Sample endpoint (secured)

docs/docs/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ from starlette.authentication import BaseUser
3838

3939
...
4040
# Takes a string that will look like 'Bearer eyJhbGc...'
41-
def verify_authorization_header(auth_header: str) -> Tuple[List[str], BaseUser]: # Returns a Tuple of a List of scopes (string) and a BaseUser
41+
def verify_header(headers: List[str]) -> Tuple[List[str], BaseUser]: # Returns a Tuple of a List of scopes (string) and a BaseUser
4242
user = FastAPIUser(first_name="Code", last_name="Specialist", user_id=1) # Usually you would decode the JWT here and verify its signature to extract the 'sub'
4343
scopes = [] # You could for instance use the scopes provided in the JWT or request them by looking up the scopes with the 'sub' somewhere
4444
return scopes, user
@@ -53,7 +53,7 @@ from fastapi_auth_middleware import AuthMiddleware
5353
...
5454

5555
app = FastAPI()
56-
app.add_middleware(AuthMiddleware, verify_authorization_header=verify_authorization_header)
56+
app.add_middleware(AuthMiddleware, verify_header=verify_header)
5757
```
5858

5959
After adding this middleware, all requests will pass the `verify_authorization_header` function and contain the **scopes** as well as the **user object** as injected dependencies.

examples/simple.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Tuple, List
1+
from typing import Tuple, List, Dict
22
import uvicorn
33
from fastapi import FastAPI
44
from starlette.requests import Request
@@ -7,14 +7,14 @@
77

88

99
# The method you have to provide
10-
def verify_authorization_header(auth_header: str) -> Tuple[List[str], FastAPIUser]:
10+
def verify_header(headers: Dict) -> Tuple[List[str], FastAPIUser]:
1111
user = FastAPIUser(first_name="Code", last_name="Specialist", user_id=1) # Usually you would decode the JWT here and verify its signature to extract the 'sub'
1212
scopes = [] # You could for instance use the scopes provided in the JWT or request them by looking up the scopes with the 'sub' somewhere
1313
return scopes, user
1414

1515

1616
app = FastAPI()
17-
app.add_middleware(AuthMiddleware, verify_authorization_header=verify_authorization_header) # Add the middleware with your verification method to the whole application
17+
app.add_middleware(AuthMiddleware, verify_header=verify_header) # Add the middleware with your verification method to the whole application
1818

1919

2020
@app.get('/') # Sample endpoint (secured)

examples/simple_with_scopes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Tuple, List
1+
from typing import Tuple, List, Dict
22
import uvicorn
33
from fastapi import FastAPI
44
from starlette.authentication import requires
@@ -8,14 +8,14 @@
88

99

1010
# The method you have to provide
11-
def verify_authorization_header(auth_header: str) -> Tuple[List[str], FastAPIUser]:
11+
def verify_header(headers: Dict) -> Tuple[List[str], FastAPIUser]:
1212
user = FastAPIUser(first_name="Code", last_name="Specialist", user_id=1) # Usually you would decode the JWT here and verify its signature to extract the 'sub'
1313
scopes = ["admin"] # You could for instance use the scopes provided in the JWT or request them by looking up the scopes with the 'sub' somewhere
1414
return scopes, user
1515

1616

1717
app = FastAPI()
18-
app.add_middleware(AuthMiddleware, verify_authorization_header=verify_authorization_header) # Add the middleware with your verification method to the whole application
18+
app.add_middleware(AuthMiddleware, verify_header=verify_header) # Add the middleware with your verification method to the whole application
1919

2020

2121
@app.get('/home') # Sample endpoint (secured)

fastapi_auth_middleware/middleware.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import inspect
2-
from typing import Tuple, Callable, List
2+
from typing import Tuple, Callable, List, Dict
33

44
from fastapi import FastAPI
55
from starlette.authentication import AuthenticationBackend, AuthCredentials, AuthenticationError, BaseUser
@@ -46,13 +46,13 @@ def identity(self) -> str:
4646
class FastAPIAuthBackend(AuthenticationBackend):
4747
""" Auth Backend for FastAPI """
4848

49-
def __init__(self, verify_authorization_header: Callable[[str], Tuple[List[str], BaseUser]]):
49+
def __init__(self, verify_header: Callable[[Dict], Tuple[List[str], BaseUser]]):
5050
""" Auth Backend constructor. Part of an AuthenticationMiddleware as backend.
5151
5252
Args:
53-
verify_authorization_header (callable): A function handle that returns a list of scopes and a BaseUser
53+
verify_header (callable): A function handle that returns a list of scopes and a BaseUser
5454
"""
55-
self.verify_authorization_header = verify_authorization_header
55+
self.verify_header = verify_header
5656

5757
async def authenticate(self, conn: HTTPConnection) -> Tuple[AuthCredentials, BaseUser]:
5858
""" The 'magic' happens here. The authenticate method is invoked each time a route is called that the middleware is applied to.
@@ -67,11 +67,10 @@ async def authenticate(self, conn: HTTPConnection) -> Tuple[AuthCredentials, Bas
6767
raise AuthenticationError("Authorization header missing")
6868

6969
try:
70-
authorization_header: str = conn.headers["Authorization"]
71-
if inspect.iscoroutinefunction(self.verify_authorization_header):
72-
scopes, user = await self.verify_authorization_header(authorization_header)
70+
if inspect.iscoroutinefunction(self.verify_header):
71+
scopes, user = await self.verify_header(conn.headers)
7372
else:
74-
scopes, user = self.verify_authorization_header(authorization_header)
73+
scopes, user = self.verify_header(conn.headers)
7574

7675
except Exception as exception:
7776
raise AuthenticationError(exception) from None
@@ -82,15 +81,15 @@ async def authenticate(self, conn: HTTPConnection) -> Tuple[AuthCredentials, Bas
8281
# noinspection PyPep8Naming
8382
def AuthMiddleware(
8483
app: FastAPI,
85-
verify_authorization_header: Callable[[str], Tuple[List[str], BaseUser]],
84+
verify_header: Callable[[str], Tuple[List[str], BaseUser]],
8685
auth_error_handler: Callable[[Request, AuthenticationError], JSONResponse] = None
8786
):
8887
""" Factory method, returning an AuthenticationMiddleware
8988
Intentionally not named with lower snake case convention as this is a factory method returning a class. Should feel like a class.
9089
9190
Args:
9291
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.
93-
verify_authorization_header (Callable[[str], Tuple[List[str], BaseUser]]): A function handle that returns a list of scopes and a BaseUser
92+
verify_header (Callable[[str], Tuple[List[str], BaseUser]]): A function handle that returns a list of scopes and a BaseUser
9493
auth_error_handler (Callable[[Request, Exception], JSONResponse]): Optional error handler for creating responses when an exception was raised in verify_authorization_header
9594
9695
Examples:
@@ -104,4 +103,4 @@ def verify_authorization_header(auth_header: str) -> Tuple[List[str], FastAPIUse
104103
app.add_middleware(AuthMiddleware, verify_authorization_header=verify_authorization_header)
105104
```
106105
"""
107-
return AuthenticationMiddleware(app, backend=FastAPIAuthBackend(verify_authorization_header), on_error=auth_error_handler)
106+
return AuthenticationMiddleware(app, backend=FastAPIAuthBackend(verify_header), on_error=auth_error_handler)

tests/test_middleware.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Callable
1+
from typing import Callable, List, Dict
22

33
from _pytest.fixtures import fixture
44
from fastapi import FastAPI
@@ -11,13 +11,13 @@
1111

1212

1313
# Sample verification function, does nothing of effect
14-
def verify_authorization_header_basic(auth_header: str):
14+
def verify_header(headers: Dict):
1515
user = FastAPIUser(first_name="Code", last_name="Specialist", user_id=1)
1616
scopes = ["authenticated"]
1717
return scopes, user
1818

1919

20-
async def verify_authorization_header_basic_admin_scope(auth_header: str):
20+
async def verify_header_basic_admin_scope(headers: List[str]):
2121
user = FastAPIUser(first_name="Code", last_name="Specialist", user_id=1)
2222
scopes = ["admin"]
2323
return scopes, user
@@ -28,9 +28,9 @@ 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_authorization_header: Callable, auth_error_handler: Callable = None):
31+
def fastapi_app(verify_header: Callable, auth_error_handler: Callable = None):
3232
app = FastAPI()
33-
app.add_middleware(AuthMiddleware, verify_authorization_header=verify_authorization_header, auth_error_handler=auth_error_handler)
33+
app.add_middleware(AuthMiddleware, verify_header=verify_header, auth_error_handler=auth_error_handler)
3434

3535
@app.get("/")
3636
def home():
@@ -57,12 +57,12 @@ class TestBasicBehaviour:
5757

5858
@fixture
5959
def client(self) -> TestClient:
60-
app = fastapi_app(verify_authorization_header_basic)
60+
app = fastapi_app(verify_header)
6161
return TestClient(app)
6262

6363
@fixture
6464
def client_with_scopes(self) -> TestClient:
65-
app = fastapi_app(verify_authorization_header_basic_admin_scope)
65+
app = fastapi_app(verify_header_basic_admin_scope)
6666
return TestClient(app)
6767

6868
def test_home_fail_no_header(self, client: TestClient):
@@ -81,7 +81,7 @@ def test_scopes(self, client: TestClient, client_with_scopes: TestClient):
8181
assert client_with_scopes.get("/admin-scope", headers={"Authorization": "ey.."}).status_code == 200 # Contains the requested scope
8282

8383
def test_fail_auth_error(self):
84-
app = fastapi_app(verify_authorization_header=raise_exception_in_verify_authorization_header)
84+
app = fastapi_app(verify_header=raise_exception_in_verify_authorization_header)
8585
client_with_auth_error = TestClient(app=app)
8686

8787
response = client_with_auth_error.get('/', headers={"Authorization": "ey.."})
@@ -92,7 +92,7 @@ def handle_auth_error(request: Request, exception: AuthenticationError):
9292
assert isinstance(exception, AuthenticationError)
9393
return JSONResponse(content={'message': str(exception)}, status_code=401)
9494

95-
app = fastapi_app(verify_authorization_header=raise_exception_in_verify_authorization_header, auth_error_handler=handle_auth_error)
95+
app = fastapi_app(verify_header=raise_exception_in_verify_authorization_header, auth_error_handler=handle_auth_error)
9696
client_with_auth_error = TestClient(app=app)
9797

9898
response = client_with_auth_error.get('/', headers={"Authorization": "ey.."})

0 commit comments

Comments
 (0)