Skip to content

Commit 0b44ea7

Browse files
romaindupJonasKs
authored andcommitted
Add support for extracting authentication params from www-authenticate header
1 parent fe932f7 commit 0b44ea7

File tree

3 files changed

+32
-5
lines changed

3 files changed

+32
-5
lines changed

fastapi_azure_auth/auth.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,12 @@ async def __call__(self, request: HTTPConnection, security_scopes: SecurityScope
165165
claims: dict[str, Any] = get_unverified_claims(access_token)
166166
except Exception as error:
167167
log.warning('Malformed token received. %s. Error: %s', access_token, error, exc_info=True)
168-
raise Unauthorized(detail='Invalid token format', request=request) from error
168+
raise Unauthorized(
169+
detail='Invalid token format',
170+
client_id=self.app_client_id,
171+
authorization_url=self.authorization_url,
172+
request=request,
173+
) from error
169174

170175
user_is_guest: bool = is_guest(claims=claims)
171176
if not self.allow_guest_users and user_is_guest:

fastapi_azure_auth/exceptions.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,16 @@ def __init__(self, detail: str) -> None:
2525
class UnauthorizedHttp(HTTPException):
2626
"""HTTP exception for authentication failures"""
2727

28-
def __init__(self, detail: str) -> None:
28+
def __init__(self, detail: str, authorization_url: str | None = None, client_id: str | None = None) -> None:
29+
header_value = 'Bearer'
30+
if authorization_url:
31+
header_value += f', authorization_uri="{authorization_url}"'
32+
if client_id:
33+
header_value += f', client_id="{client_id}"'
2934
super().__init__(
3035
status_code=status.HTTP_401_UNAUTHORIZED,
3136
detail={"error": "invalid_token", "message": detail},
32-
headers={"WWW-Authenticate": "Bearer"},
37+
headers={"WWW-Authenticate": header_value},
3338
)
3439

3540

@@ -103,10 +108,12 @@ def InvalidRequest(detail: str, request: HTTPConnection) -> InvalidRequestHttp |
103108
return InvalidRequestWebSocket(detail)
104109

105110

106-
def Unauthorized(detail: str, request: HTTPConnection) -> UnauthorizedHttp | UnauthorizedWebSocket:
111+
def Unauthorized(
112+
detail: str, request: HTTPConnection, authorization_url: str | None = None, client_id: str | None = None
113+
) -> UnauthorizedHttp | UnauthorizedWebSocket:
107114
"""Factory function for unauthorized exceptions"""
108115
if request.scope["type"] == "http":
109-
return UnauthorizedHttp(detail)
116+
return UnauthorizedHttp(detail, authorization_url, client_id)
110117
return UnauthorizedWebSocket(detail)
111118

112119

tests/single_tenant/test_single_tenant.py

+15
Original file line numberDiff line numberDiff line change
@@ -316,3 +316,18 @@ async def test_change_of_keys_works(single_tenant_app, mock_openid_ok_then_empty
316316
'detail': {'error': 'invalid_token', 'message': 'Unable to verify token, no signing keys found'}
317317
}
318318
assert second_resonse.status_code == 401
319+
320+
321+
@pytest.mark.anyio
322+
async def test_authentication_params_from_header(single_tenant_app):
323+
"""
324+
* Send request with "Authorization: Bearer"
325+
* Validate response provides authorization uri and client id in header
326+
"""
327+
async with AsyncClient(app=app, base_url='http://test', headers={'Authorization': 'Bearer'}) as ac:
328+
response = await ac.get('api/v1/hello')
329+
assert response.status_code == 401
330+
assert (
331+
response.headers['www-authenticate']
332+
== 'Bearer, authorization_uri="https://login.microsoftonline.com/intility_tenant_id/oauth2/v2.0/authorize", client_id="oauth299-9999-9999-abcd-efghijkl1234567890"'
333+
)

0 commit comments

Comments
 (0)