This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 54259ae1795 Verify Azure AD OAuth id_token signatures by default in 
FAB auth manager (#69374)
54259ae1795 is described below

commit 54259ae1795eb460835559af3ecbab35d5e65cad
Author: Jarek Potiuk <[email protected]>
AuthorDate: Tue Jul 7 11:59:39 2026 +0200

    Verify Azure AD OAuth id_token signatures by default in FAB auth manager 
(#69374)
    
    * Verify Azure AD OAuth id_token signatures by default in FAB auth manager
    
    The FAB auth manager's Azure AD OAuth provider decoded the incoming id_token
    with verify_signature defaulting to False, so the token payload was accepted
    without verifying its signature unless a deployment explicitly set the 
option.
    The Authentik OAuth provider in the same security manager already defaults
    verify_signature to True.
    
    Default verify_signature to True for the Azure AD provider, so id_token
    signatures are verified against the Microsoft JWKS by default, consistent 
with
    the Authentik provider, and log a warning when signature verification is
    skipped.
    
    Deployments that intentionally relied on the previous default must set
    verify_signature: False explicitly in the Azure provider client_kwargs to 
keep
    the old behaviour.
    
    Generated-by: Claude Opus 4.8 (1M context) following the guidelines at
    
https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions
    
    * Note Azure AD id_token signature verification default in FAB changelog
    
    Generated-by: Claude Opus 4.8 (1M context) following the guidelines at
    
https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions
    
    * Add "Authentik" to the docs spelling wordlist
    
    The FAB changelog note references the Authentik OAuth provider by name,
    which the spellchecker rejects as an unknown word. Authentik is a proper
    product name, so add it to the shared wordlist.
    
    Generated-by: Claude Opus 4.8 (1M context)
---
 docs/spelling_wordlist.txt                         |  1 +
 providers/fab/docs/changelog.rst                   |  7 +++++
 .../fab/auth_manager/security_manager/override.py  |  3 +-
 .../auth_manager/security_manager/test_override.py | 32 ++++++++++++++++++++++
 4 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index d1299ad03ce..2b7c13ee660 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -121,6 +121,7 @@ auditable
 Auth
 auth
 authenticator
+Authentik
 Authlib
 authMechanism
 authorised
diff --git a/providers/fab/docs/changelog.rst b/providers/fab/docs/changelog.rst
index cc1093fa143..df7d7d402ed 100644
--- a/providers/fab/docs/changelog.rst
+++ b/providers/fab/docs/changelog.rst
@@ -20,6 +20,13 @@
 Changelog
 ---------
 
+.. note::
+    The Azure AD OAuth provider in the FAB auth manager now verifies the 
``id_token``
+    signature by default: ``verify_signature`` now defaults to ``True`` 
(previously
+    ``False``), consistent with the Authentik provider. Deployments that 
intentionally
+    relied on skipping signature verification must set ``verify_signature: 
False``
+    explicitly in the Azure provider ``client_kwargs`` to keep the previous 
behaviour.
+
 3.7.2
 .....
 
diff --git 
a/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
 
b/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
index 9c85c2e0151..41ab5a9faac 100644
--- 
a/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
+++ 
b/providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
@@ -2424,7 +2424,7 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
         return requests.get(MICROSOFT_KEY_SET_URL, timeout=30).json()
 
     def _decode_and_validate_azure_jwt(self, id_token: str) -> dict[str, str]:
-        verify_signature = 
self.oauth_remotes["azure"].client_kwargs.get("verify_signature", False)
+        verify_signature = 
self.oauth_remotes["azure"].client_kwargs.get("verify_signature", True)
         if verify_signature:
             from authlib.jose import JsonWebKey, jwt as authlib_jwt
 
@@ -2433,6 +2433,7 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
             claims.validate()
             return claims
 
+        log.warning("JWT token is not validated!")
         _parts = id_token.split(".")
         _payload = _parts[1] + "=" * (-len(_parts[1]) % 4)
         return json.loads(base64.urlsafe_b64decode(_payload))
diff --git 
a/providers/fab/tests/unit/fab/auth_manager/security_manager/test_override.py 
b/providers/fab/tests/unit/fab/auth_manager/security_manager/test_override.py
index 76849374fe7..924ec22f6ab 100644
--- 
a/providers/fab/tests/unit/fab/auth_manager/security_manager/test_override.py
+++ 
b/providers/fab/tests/unit/fab/auth_manager/security_manager/test_override.py
@@ -472,6 +472,38 @@ class TestFabAirflowSecurityManagerOverride:
             assert user_info["email"] == "[email protected]"
             assert user_info["role_keys"] == ["admin-group", "viewer-group"]
 
+    def test_decode_and_validate_azure_jwt_verifies_signature_by_default(self):
+        """Azure AD id_token signatures are verified by default 
(verify_signature defaults to True)."""
+        sm = EmptySecurityManager()
+        # client_kwargs does not set verify_signature -> it must default to 
verifying
+        sm.oauth_remotes = {"azure": Mock(client_kwargs={})}
+
+        with mock.patch.object(
+            EmptySecurityManager, "_get_microsoft_jwks", 
side_effect=RuntimeError("verify-branch-reached")
+        ) as mock_jwks:
+            with pytest.raises(RuntimeError, match="verify-branch-reached"):
+                sm._decode_and_validate_azure_jwt("header.payload.signature")
+
+        # entering the verifying branch means the Microsoft JWKS were fetched
+        mock_jwks.assert_called_once()
+
+    def 
test_decode_and_validate_azure_jwt_skips_verification_when_opted_out(self):
+        """With verify_signature explicitly False, the token is decoded 
without signature verification."""
+        import base64
+        import json as _json
+
+        payload = base64.urlsafe_b64encode(_json.dumps({"oid": 
"user-1"}).encode()).decode().rstrip("=")
+        id_token = f"header.{payload}.signature"
+
+        sm = EmptySecurityManager()
+        sm.oauth_remotes = {"azure": Mock(client_kwargs={"verify_signature": 
False})}
+
+        with mock.patch.object(EmptySecurityManager, "_get_microsoft_jwks") as 
mock_jwks:
+            result = sm._decode_and_validate_azure_jwt(id_token)
+
+        mock_jwks.assert_not_called()
+        assert result == {"oid": "user-1"}
+
 
 def test_ldap_search_escapes_username_and_validates_filter():
     """Test that LDAP search properly escapes username and validates search 
filter."""

Reply via email to