-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HTTPBearer token is set, Auth button not shown on /api/docs #67
Changes from 23 commits
4ae5b2d
a611595
ee5085b
23b0e16
bb5588b
8643e6c
8382c10
37433f3
08c14b5
1239b56
27e6a7f
effa3d8
588231b
b3f4957
2e45752
6079861
f9e4f4c
a64b4ea
a7103f7
feac762
41b1cd0
576f944
ce63c77
e163eac
eeffe05
96ad751
944473b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ | |
|
||
from fastapi import HTTPException | ||
from fastapi.requests import Request | ||
from fastapi.security.http import HTTPBearer | ||
from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBearer | ||
from httpx import AsyncClient, NetworkError | ||
from pydantic import BaseModel | ||
from starlette.requests import ClientDisconnect, HTTPConnection | ||
|
@@ -126,7 +126,7 @@ class Authentication(ABC): | |
""" | ||
|
||
@abstractmethod | ||
async def authenticate(self, request: HTTPConnection, token: str | None = None) -> dict | None: | ||
async def authenticate(self, request: Request, token: str | None = None) -> dict | None: | ||
"""Authenticate the user.""" | ||
pass | ||
|
||
|
@@ -142,17 +142,24 @@ async def extract(self, request: Request) -> str | None: | |
pass | ||
|
||
|
||
class HttpBearerExtractor(IdTokenExtractor): | ||
class HttpBearerExtractor(HTTPBearer, IdTokenExtractor): | ||
"""Extracts bearer tokens using FastAPI's HTTPBearer. | ||
|
||
Specifically designed for HTTP Authorization header token extraction. | ||
""" | ||
|
||
def __init__(self, auto_error: bool = False): | ||
super().__init__(auto_error=auto_error) | ||
|
||
async def __call__(self, request: Request) -> Optional[HTTPAuthorizationCredentials]: | ||
"""Extract the Authorization header from the request.""" | ||
return await super().__call__(request) | ||
|
||
async def extract(self, request: Request) -> str | None: | ||
http_bearer = HTTPBearer(auto_error=False) | ||
credential = await http_bearer(request) | ||
"""Extract the token from the Authorization header in the request.""" | ||
http_auth_credentials = await super().__call__(request) | ||
|
||
return credential.credentials if credential else None | ||
return http_auth_credentials.credentials if http_auth_credentials else None | ||
|
||
|
||
class OIDCAuth(Authentication): | ||
|
@@ -168,11 +175,7 @@ def __init__( | |
resource_server_id: str, | ||
resource_server_secret: str, | ||
oidc_user_model_cls: type[OIDCUserModel], | ||
id_token_extractor: IdTokenExtractor | None = None, | ||
): | ||
if not id_token_extractor: | ||
self.id_token_extractor = HttpBearerExtractor() | ||
|
||
self.openid_url = openid_url | ||
self.openid_config_url = openid_config_url | ||
self.resource_server_id = resource_server_id | ||
|
@@ -181,7 +184,7 @@ def __init__( | |
|
||
self.openid_config: OIDCConfig | None = None | ||
|
||
async def authenticate(self, request: HTTPConnection, token: str | None = None) -> OIDCUserModel | None: | ||
async def authenticate(self, request: Request, token: str | None = None) -> OIDCUserModel | None: | ||
"""Return the OIDC user from OIDC introspect endpoint. | ||
|
||
This is used as a security module in Fastapi projects | ||
|
@@ -197,33 +200,16 @@ async def authenticate(self, request: HTTPConnection, token: str | None = None) | |
if not oauth2lib_settings.OAUTH2_ACTIVE: | ||
return None | ||
|
||
async with AsyncClient(http1=True, verify=HTTPX_SSL_CONTEXT) as async_client: | ||
await self.check_openid_config(async_client) | ||
|
||
# Handle WebSocket requests separately only to check for token presence. | ||
if isinstance(request, WebSocket): | ||
if token is None: | ||
raise HTTPException( | ||
status_code=HTTPStatus.FORBIDDEN, | ||
detail="Not authenticated", | ||
) | ||
token_or_extracted_id_token = token | ||
else: | ||
request = cast(Request, request) | ||
|
||
if await self.is_bypassable_request(request): | ||
return None | ||
if await self.is_bypassable_request(request): | ||
return None | ||
|
||
if token is None: | ||
extracted_id_token = await self.id_token_extractor.extract(request) | ||
if not extracted_id_token: | ||
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Not authenticated") | ||
if not token: | ||
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Not authenticated") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you did this because the new WebSocket implementation passes the token in a header. Nice! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct :) And it no longer servers only for the websocket since that should not matter |
||
|
||
token_or_extracted_id_token = extracted_id_token | ||
else: | ||
token_or_extracted_id_token = token | ||
async with AsyncClient(http1=True, verify=HTTPX_SSL_CONTEXT) as async_client: | ||
await self.check_openid_config(async_client) | ||
|
||
user_info: OIDCUserModel = await self.userinfo(async_client, token_or_extracted_id_token) | ||
user_info: OIDCUserModel = await self.userinfo(async_client, token) | ||
logger.debug("OIDCUserModel object.", user_info=user_info) | ||
return user_info | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe keep the kwarg in
__init__
and when a value is passed log a (deprecation) warning instead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great spot by you (@Igoranze): if a custom
id_token_extractor
is passed then it's not actually assigned toself.id_token_extractor
soauthenticate()
raises anAttributeError
when trying to call it.The only way this can have worked for others is if they override
authenticate()
as well. So, we don't need to worry about backwards compatibility