@@ -62,6 +62,31 @@ def temp_dir_fixture(monkeypatch, tmp_path):
6262 # Update file paths to use the test's tmp_path
6363 monkeypatch .setattr (auth , "auth_file" , str (tmp_path / "auth.json" ))
6464 monkeypatch .setattr (auth , "cache_file" , str (tmp_path / "cache.bin" ))
65+
66+ # Bypass JWKS signature validation in tests — fake JWTs cannot pass
67+ # real signature checks. Decode claims via base64 like the removed
68+ # _decode_jwt_claims helper.
69+ def _test_decode_jwt_token (self , token , expected_audience = None ):
70+ """Test-only: decode JWT payload without signature validation."""
71+ parts = token .split ("." )
72+ if len (parts ) < 2 :
73+ raise FabricCLIError (
74+ ErrorMessages .Auth .jwt_decode_failed (),
75+ con .ERROR_AUTHENTICATION_FAILED ,
76+ )
77+ payload = parts [1 ]
78+ payload += "=" * ((- len (payload )) % 4 )
79+ try :
80+ decoded = base64 .urlsafe_b64decode (payload )
81+ return _json .loads (decoded )
82+ except Exception :
83+ raise FabricCLIError (
84+ ErrorMessages .Auth .jwt_decode_failed (),
85+ con .ERROR_AUTHENTICATION_FAILED ,
86+ )
87+
88+ monkeypatch .setattr (auth , "_decode_jwt_token" , lambda token , expected_audience = None : _test_decode_jwt_token (auth , token , expected_audience ))
89+
6590 return str (tmp_path )
6691
6792
@@ -596,28 +621,29 @@ def test_login_clears_credential_on_tenant_change(
596621
597622
598623class TestJwtClaimsDecoding :
599- """Test the _decode_jwt_claims helper ."""
624+ """Test JWT claim extraction via _decode_jwt_token (with test fixture bypassing signature validation) ."""
600625
601626 def test_valid_jwt_extracts_claims (self , temp_dir_fixture ):
602627 """Should decode tid and oid from a valid JWT."""
603628 token = _make_jwt (tid = "my-tenant" , oid = "my-oid" )
604629 auth = FabAuth ()
605- claims = auth ._decode_jwt_claims (token )
630+ claims = auth ._decode_jwt_token (token )
606631 assert claims ["tid" ] == "my-tenant"
607632 assert claims ["oid" ] == "my-oid"
608633
609- def test_invalid_jwt_returns_empty (self , temp_dir_fixture ):
610- """Should return empty dict for malformed tokens."""
634+ def test_invalid_jwt_raises (self , temp_dir_fixture ):
635+ """Should raise FabricCLIError for malformed tokens."""
611636 auth = FabAuth ()
612- assert auth ._decode_jwt_claims ("not-a-jwt" ) == {}
613- assert auth ._decode_jwt_claims ("" ) == {}
614- assert auth ._decode_jwt_claims ("a.!!!.c" ) == {}
637+ with pytest .raises ((FabricCLIError , Exception )):
638+ auth ._decode_jwt_token ("not-a-jwt" )
639+ with pytest .raises ((FabricCLIError , Exception )):
640+ auth ._decode_jwt_token ("" )
615641
616642 def test_jwt_with_extra_claims (self , temp_dir_fixture ):
617643 """Should extract additional claims."""
618644 token = _make_jwt (tid = "t1" , oid = "o1" , upn = "user@contoso.com" )
619645 auth = FabAuth ()
620- claims = auth ._decode_jwt_claims (token )
646+ claims = auth ._decode_jwt_token (token )
621647 assert claims ["upn" ] == "user@contoso.com"
622648
623649
@@ -664,7 +690,7 @@ def test_login_rejects_malformed_token(self, mock_class, temp_dir_fixture):
664690 mock_token .expires_on = int (time .time ()) + 3600
665691 mock_class .return_value .get_token .return_value = mock_token
666692 auth = FabAuth ()
667- with pytest .raises (FabricCLIError , match = "Unable to validate " ):
693+ with pytest .raises (FabricCLIError , match = "Failed to decode JWT " ):
668694 auth .set_azure_cli ()
669695
670696 @patch ("fabric_cli.core.fab_auth.AzureCliCredential" )
@@ -680,7 +706,7 @@ def test_acquisition_rejects_token_missing_claims(self, mock_class, temp_dir_fix
680706 bad_token .token = "not-a-jwt"
681707 bad_token .expires_on = int (time .time ()) + 3600
682708 mock_class .return_value .get_token .return_value = bad_token
683- with pytest .raises (FabricCLIError , match = "Unable to validate " ):
709+ with pytest .raises (FabricCLIError , match = "Failed to decode JWT " ):
684710 auth .acquire_token (con .SCOPE_FABRIC_DEFAULT )
685711
686712
0 commit comments