Skip to content

Commit c50220a

Browse files
Implement security module with support for cookies (GH-10)
2 parents 8bc7781 + 0deef03 commit c50220a

File tree

5 files changed

+83
-4
lines changed

5 files changed

+83
-4
lines changed

Diff for: README.md

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ the [social-core](https://github.com/python-social-auth/social-core) authenticat
1313

1414
- Use multiple OAuth2 providers at the same time
1515
* There need to be provided a way to configure the OAuth2 for multiple providers
16-
- Provide `fastapi.security.*` implementations that use cookies
1716
- Token -> user data, user data -> token easy conversion
1817
- Customizable OAuth2 routes
1918
- Registration support

Diff for: examples/demonstration/router.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import json
22

33
from fastapi import Depends
4-
from fastapi import Request, APIRouter
4+
from fastapi import Request
5+
from fastapi import APIRouter
56
from fastapi.responses import HTMLResponse
6-
from fastapi.security import OAuth2
77
from fastapi.templating import Jinja2Templates
88

9+
from fastapi_oauth2.security import OAuth2
10+
911
oauth2 = OAuth2()
1012
router = APIRouter()
1113
templates = Jinja2Templates(directory="templates")

Diff for: src/fastapi_oauth2/middleware.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ async def authenticate(self, request: Request) -> Optional[Tuple["Auth", "User"]
9595
return Auth(), User()
9696

9797
user = Auth.jwt_decode(param)
98-
return Auth(user.pop("scope")), User(user)
98+
return Auth(user.pop("scope", [])), User(user)
9999

100100

101101
class OAuth2Middleware:

Diff for: src/fastapi_oauth2/security.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from fastapi.security import OAuth2 as FastAPIOAuth2
2+
from fastapi.security import OAuth2AuthorizationCodeBearer as FastAPICodeBearer
3+
from fastapi.security import OAuth2PasswordBearer as FastAPIPasswordBearer
4+
from starlette.datastructures import Headers
5+
from starlette.requests import Request
6+
7+
8+
def use_cookie(cls: FastAPIOAuth2):
9+
def _use_cookie(*args, **kwargs):
10+
async def __call__(self, request: Request):
11+
authorization = request.headers.get("Authorization", request.cookies.get("Authorization"))
12+
if authorization:
13+
request._headers = Headers({**request.headers, "Authorization": authorization})
14+
return await super(cls, self).__call__(request)
15+
16+
cls.__call__ = __call__
17+
return cls(*args, **kwargs)
18+
19+
return _use_cookie
20+
21+
22+
@use_cookie
23+
class OAuth2(FastAPIOAuth2):
24+
...
25+
26+
27+
@use_cookie
28+
class OAuth2PasswordBearer(FastAPIPasswordBearer):
29+
...
30+
31+
32+
@use_cookie
33+
class OAuth2AuthorizationCodeBearer(FastAPICodeBearer):
34+
...

Diff for: tests/test_oauth2_middleware.py

+44
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,47 @@
11
import pytest
2+
from fastapi import APIRouter
3+
from fastapi import Depends
24
from fastapi import FastAPI
5+
from fastapi import Request
36
from httpx import AsyncClient
47
from social_core.backends.github import GithubOAuth2
8+
from starlette.responses import Response
59

610
from fastapi_oauth2.client import OAuth2Client
711
from fastapi_oauth2.core import OAuth2Core
812
from fastapi_oauth2.middleware import OAuth2Middleware
913
from fastapi_oauth2.router import router as oauth2_router
14+
from fastapi_oauth2.security import OAuth2
1015

1116
app = FastAPI()
17+
oauth2 = OAuth2()
18+
app_router = APIRouter()
1219

20+
21+
@app_router.get("/user")
22+
def user(request: Request, _: str = Depends(oauth2)):
23+
return request.user
24+
25+
26+
@app_router.get("/auth")
27+
def auth(request: Request):
28+
access_token = request.auth.jwt_create({
29+
"name": "test",
30+
"sub": "test",
31+
"id": "test",
32+
})
33+
response = Response()
34+
response.set_cookie(
35+
"Authorization",
36+
value=f"Bearer {access_token}",
37+
max_age=request.auth.expires,
38+
expires=request.auth.expires,
39+
httponly=request.auth.http,
40+
)
41+
return response
42+
43+
44+
app.include_router(app_router)
1345
app.include_router(oauth2_router)
1446
app.add_middleware(OAuth2Middleware, config={
1547
"allow_http": True,
@@ -30,6 +62,18 @@ async def test_auth_redirect():
3062
assert response.status_code == 303 # Redirect
3163

3264

65+
@pytest.mark.anyio
66+
async def test_authenticated_request():
67+
async with AsyncClient(app=app, base_url="http://test") as client:
68+
response = await client.get("/user")
69+
assert response.status_code == 403 # Forbidden
70+
71+
await client.get("/auth") # Simulate login
72+
73+
response = await client.get("/user")
74+
assert response.status_code == 200 # OK
75+
76+
3377
@pytest.mark.anyio
3478
async def test_core_init(backends):
3579
for backend in backends:

0 commit comments

Comments
 (0)