From 9b4de0a1eea05960a217b6960814e1260e3bdcc4 Mon Sep 17 00:00:00 2001 From: Gabriel Gore Date: Tue, 4 Nov 2025 13:00:55 -0500 Subject: [PATCH 01/10] pass HTTPX client config --- fastapi_azure_auth/openid_config.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/fastapi_azure_auth/openid_config.py b/fastapi_azure_auth/openid_config.py index 470fc4a..6803393 100644 --- a/fastapi_azure_auth/openid_config.py +++ b/fastapi_azure_auth/openid_config.py @@ -1,6 +1,8 @@ import logging +import ssl from datetime import datetime, timedelta from typing import TYPE_CHECKING, Any, Dict, List, Optional +from typing import TYPE_CHECKING, Any, Dict, List, NotRequired, Optional, TypedDict import jwt from fastapi import HTTPException, status @@ -12,6 +14,10 @@ log = logging.getLogger('fastapi_azure_auth') +class HttpClientConfig(TypedDict): + verify: NotRequired[ssl.SSLContext] + + class OpenIdConfig: def __init__( self, @@ -19,12 +25,16 @@ def __init__( multi_tenant: bool = False, app_id: Optional[str] = None, config_url: Optional[str] = None, + http_client_config: Optional[HttpClientConfig] = None, ) -> None: self.tenant_id: Optional[str] = tenant_id self._config_timestamp: Optional[datetime] = None self.multi_tenant: bool = multi_tenant self.app_id = app_id self.config_url = config_url + self.http_client_config: HttpClientConfig = ( + http_client_config or HttpClientConfig() + ) self.authorization_endpoint: str self.signing_keys: dict[str, 'AllowedPublicKeys'] @@ -72,7 +82,7 @@ async def _load_openid_config(self) -> None: if self.app_id: config_url += f'?appid={self.app_id}' - async with AsyncClient(timeout=10) as client: + async with AsyncClient(timeout=10, **self.http_client_config) as client: log.info('Fetching OpenID Connect config from %s', config_url) openid_response = await client.get(config_url) openid_response.raise_for_status() From 6e5322bf53eafd271085d24c3ade53364cad0ada Mon Sep 17 00:00:00 2001 From: Gabriel Gore Date: Tue, 4 Nov 2025 13:03:13 -0500 Subject: [PATCH 02/10] add `trust_env` + `timeout` config --- fastapi_azure_auth/openid_config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fastapi_azure_auth/openid_config.py b/fastapi_azure_auth/openid_config.py index 6803393..dadd816 100644 --- a/fastapi_azure_auth/openid_config.py +++ b/fastapi_azure_auth/openid_config.py @@ -16,6 +16,8 @@ class HttpClientConfig(TypedDict): verify: NotRequired[ssl.SSLContext] + trust_env: NotRequired[bool] + timeout: NotRequired[float] class OpenIdConfig: From 573f29928fbdc04d71f581d1a0c08f7bf440bb95 Mon Sep 17 00:00:00 2001 From: Gabriel Gore Date: Tue, 4 Nov 2025 13:41:50 -0500 Subject: [PATCH 03/10] fix imports --- fastapi_azure_auth/openid_config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fastapi_azure_auth/openid_config.py b/fastapi_azure_auth/openid_config.py index dadd816..0d0f522 100644 --- a/fastapi_azure_auth/openid_config.py +++ b/fastapi_azure_auth/openid_config.py @@ -1,7 +1,6 @@ import logging import ssl from datetime import datetime, timedelta -from typing import TYPE_CHECKING, Any, Dict, List, Optional from typing import TYPE_CHECKING, Any, Dict, List, NotRequired, Optional, TypedDict import jwt From 1dd88f5e95a2b172b1b4b222078415bdcc6b8ea0 Mon Sep 17 00:00:00 2001 From: Gabriel Gore Date: Tue, 4 Nov 2025 13:51:29 -0500 Subject: [PATCH 04/10] remove `timeout` arg results in mypy error (although this seems like a mistake, it would just overwrite the default timeout value if it was provided). --- fastapi_azure_auth/openid_config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fastapi_azure_auth/openid_config.py b/fastapi_azure_auth/openid_config.py index 0d0f522..2e32496 100644 --- a/fastapi_azure_auth/openid_config.py +++ b/fastapi_azure_auth/openid_config.py @@ -16,7 +16,6 @@ class HttpClientConfig(TypedDict): verify: NotRequired[ssl.SSLContext] trust_env: NotRequired[bool] - timeout: NotRequired[float] class OpenIdConfig: From 45282615a67ddd3d79642c6e7f39f3301dc667f9 Mon Sep 17 00:00:00 2001 From: Gabriel Gore Date: Tue, 4 Nov 2025 13:53:06 -0500 Subject: [PATCH 05/10] verify can accept bool value --- fastapi_azure_auth/openid_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi_azure_auth/openid_config.py b/fastapi_azure_auth/openid_config.py index 2e32496..8049b5b 100644 --- a/fastapi_azure_auth/openid_config.py +++ b/fastapi_azure_auth/openid_config.py @@ -14,7 +14,7 @@ class HttpClientConfig(TypedDict): - verify: NotRequired[ssl.SSLContext] + verify: NotRequired[ssl.SSLContext | bool] trust_env: NotRequired[bool] From db76a20205f4e51d263178fe6372b83047cc96fa Mon Sep 17 00:00:00 2001 From: Gabriel Gore Date: Tue, 4 Nov 2025 13:54:30 -0500 Subject: [PATCH 06/10] HttpClientConfig docstring --- fastapi_azure_auth/openid_config.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fastapi_azure_auth/openid_config.py b/fastapi_azure_auth/openid_config.py index 8049b5b..bfa6839 100644 --- a/fastapi_azure_auth/openid_config.py +++ b/fastapi_azure_auth/openid_config.py @@ -14,6 +14,14 @@ class HttpClientConfig(TypedDict): + """ + Configuration for the HTTP client used to fetch the OpenID configuration. + + verify - (optional) Either `True` to use an SSL context with the default CA bundle, + `False` to disable verification, or an instance of `ssl.SSLContext` to use a custom context. + trust_env - (optional) Enables or disables usage of environment variables for configuration. + """ + verify: NotRequired[ssl.SSLContext | bool] trust_env: NotRequired[bool] From a6e01884a423be56f111a2103f814830db419018 Mon Sep 17 00:00:00 2001 From: Gabriel Gore Date: Tue, 4 Nov 2025 13:55:44 -0500 Subject: [PATCH 07/10] black formatting --- fastapi_azure_auth/openid_config.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fastapi_azure_auth/openid_config.py b/fastapi_azure_auth/openid_config.py index bfa6839..901545d 100644 --- a/fastapi_azure_auth/openid_config.py +++ b/fastapi_azure_auth/openid_config.py @@ -40,9 +40,7 @@ def __init__( self.multi_tenant: bool = multi_tenant self.app_id = app_id self.config_url = config_url - self.http_client_config: HttpClientConfig = ( - http_client_config or HttpClientConfig() - ) + self.http_client_config: HttpClientConfig = http_client_config or HttpClientConfig() self.authorization_endpoint: str self.signing_keys: dict[str, 'AllowedPublicKeys'] From a91aabe41d66ab9b67509ccf6bba6a6d25d61333 Mon Sep 17 00:00:00 2001 From: Gabriel Gore Date: Tue, 4 Nov 2025 13:58:51 -0500 Subject: [PATCH 08/10] use future annotations with typing_extensions `NotRequired` --- fastapi_azure_auth/openid_config.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fastapi_azure_auth/openid_config.py b/fastapi_azure_auth/openid_config.py index 901545d..4829f9e 100644 --- a/fastapi_azure_auth/openid_config.py +++ b/fastapi_azure_auth/openid_config.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import logging import ssl from datetime import datetime, timedelta -from typing import TYPE_CHECKING, Any, Dict, List, NotRequired, Optional, TypedDict +from typing import TYPE_CHECKING, Any, Dict, List, Optional, TypedDict import jwt from fastapi import HTTPException, status @@ -9,6 +11,7 @@ if TYPE_CHECKING: # pragma: no cover from jwt.algorithms import AllowedPublicKeys + from typing_extensions import NotRequired # added in python 3.11 log = logging.getLogger('fastapi_azure_auth') From 55db0208ab9543ff9449e9ee102579995e21cf03 Mon Sep 17 00:00:00 2001 From: Gabriel Gore Date: Tue, 4 Nov 2025 14:29:35 -0500 Subject: [PATCH 09/10] pyupgrade --- fastapi_azure_auth/openid_config.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fastapi_azure_auth/openid_config.py b/fastapi_azure_auth/openid_config.py index 4829f9e..943639a 100644 --- a/fastapi_azure_auth/openid_config.py +++ b/fastapi_azure_auth/openid_config.py @@ -3,7 +3,7 @@ import logging import ssl from datetime import datetime, timedelta -from typing import TYPE_CHECKING, Any, Dict, List, Optional, TypedDict +from typing import TYPE_CHECKING, Any, TypedDict import jwt from fastapi import HTTPException, status @@ -32,21 +32,21 @@ class HttpClientConfig(TypedDict): class OpenIdConfig: def __init__( self, - tenant_id: Optional[str] = None, + tenant_id: str | None = None, multi_tenant: bool = False, - app_id: Optional[str] = None, - config_url: Optional[str] = None, - http_client_config: Optional[HttpClientConfig] = None, + app_id: str | None = None, + config_url: str | None = None, + http_client_config: HttpClientConfig | None = None, ) -> None: - self.tenant_id: Optional[str] = tenant_id - self._config_timestamp: Optional[datetime] = None + self.tenant_id: str | None = tenant_id + self._config_timestamp: datetime | None = None self.multi_tenant: bool = multi_tenant self.app_id = app_id self.config_url = config_url self.http_client_config: HttpClientConfig = http_client_config or HttpClientConfig() self.authorization_endpoint: str - self.signing_keys: dict[str, 'AllowedPublicKeys'] + self.signing_keys: dict[str, AllowedPublicKeys] self.token_endpoint: str self.issuer: str @@ -107,7 +107,7 @@ async def _load_openid_config(self) -> None: jwks_response.raise_for_status() self._load_keys(jwks_response.json()['keys']) - def _load_keys(self, keys: List[Dict[str, Any]]) -> None: + def _load_keys(self, keys: list[dict[str, Any]]) -> None: """ Create certificates based on signing keys and store them """ From 8422ef9e75dd44949785efd0f9875450f7fac55c Mon Sep 17 00:00:00 2001 From: Gabriel Gore Date: Thu, 6 Nov 2025 08:52:30 -0500 Subject: [PATCH 10/10] pass down http_client_config from AzureAuthorizationCodeBearerBase --- fastapi_azure_auth/auth.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/fastapi_azure_auth/auth.py b/fastapi_azure_auth/auth.py index 5a8541c..3513610 100644 --- a/fastapi_azure_auth/auth.py +++ b/fastapi_azure_auth/auth.py @@ -36,6 +36,8 @@ if TYPE_CHECKING: # pragma: no cover from jwt.algorithms import AllowedPublicKeys + from fastapi_azure_auth.openid_config import HttpClientConfig + log = logging.getLogger('fastapi_azure_auth') @@ -57,6 +59,7 @@ def __init__( openid_config_url: Optional[str] = None, openapi_description: Optional[str] = None, scheme_name: str = "AzureAuthorizationCodeBearerBase", + http_client_config: Optional["HttpClientConfig"] = None, ) -> None: """ Initialize settings. @@ -107,6 +110,8 @@ def __init__( :param scheme_name: str The name of the security scheme to be used in OpenAPI documentation. Default is 'AzureAuthorizationCodeBearerBase'. + :param http_client_config: HttpClientConfig + Configuration for the HTTP client used to fetch the OpenID configuration. """ self.auto_error = auto_error # Validate settings, making sure there's no misconfigured dependencies out there @@ -123,6 +128,7 @@ def __init__( multi_tenant=self.multi_tenant, app_id=app_client_id if openid_config_use_app_id else None, config_url=openid_config_url or None, + http_client_config=http_client_config, ) self.leeway: int = leeway @@ -302,6 +308,7 @@ def __init__( openapi_token_url: Optional[str] = None, openapi_description: Optional[str] = None, scheme_name: str = "AzureAD_PKCE_single_tenant", + http_client_config: Optional["HttpClientConfig"] = None, ) -> None: """ Initialize settings for a single tenant application. @@ -340,6 +347,8 @@ def __init__( :param scheme_name: str The name of the security scheme to be used in OpenAPI documentation. Default is 'AzureAD_PKCE_single_tenant'. + :param http_client_config: HttpClientConfig + Configuration for the HTTP client used to fetch the OpenID configuration. """ super().__init__( app_client_id=app_client_id, @@ -352,6 +361,7 @@ def __init__( openapi_authorization_url=openapi_authorization_url, openapi_token_url=openapi_token_url, openapi_description=openapi_description, + http_client_config=http_client_config, ) self.scheme_name: str = scheme_name @@ -371,6 +381,7 @@ def __init__( openapi_token_url: Optional[str] = None, openapi_description: Optional[str] = None, scheme_name: str = "AzureAD_PKCE_multi_tenant", + http_client_config: Optional["HttpClientConfig"] = None, ) -> None: """ Initialize settings for a multi-tenant application. @@ -414,6 +425,8 @@ def __init__( :param scheme_name: str The name of the security scheme to be used in OpenAPI documentation. Default is 'AzureAD_PKCE_multi_tenant'. + :param http_client_config: HttpClientConfig + Configuration for the HTTP client used to fetch the OpenID configuration. """ super().__init__( app_client_id=app_client_id, @@ -428,6 +441,7 @@ def __init__( openapi_authorization_url=openapi_authorization_url, openapi_token_url=openapi_token_url, openapi_description=openapi_description, + http_client_config=http_client_config, ) self.scheme_name: str = scheme_name @@ -447,6 +461,7 @@ def __init__( openapi_token_url: Optional[str] = None, openapi_description: Optional[str] = None, scheme_name: str = "AzureAD_PKCE_B2C_multi_tenant", + http_client_config: Optional["HttpClientConfig"] = None, ) -> None: """ Initialize settings for a B2C multi-tenant application. @@ -485,6 +500,8 @@ def __init__( :param scheme_name: str The name of the security scheme to be used in OpenAPI documentation. Default is 'AzureAD_PKCE_B2C_multi_tenant'. + :param http_client_config: HttpClientConfig + Configuration for the HTTP client used to fetch the OpenID configuration. """ super().__init__( app_client_id=app_client_id, @@ -500,5 +517,6 @@ def __init__( openapi_authorization_url=openapi_authorization_url, openapi_token_url=openapi_token_url, openapi_description=openapi_description, + http_client_config=http_client_config, ) self.scheme_name: str = scheme_name