@@ -701,3 +701,65 @@ def test_ciam_issuer_host_via_b2c_check(self, tenant_discovery_mock):
701701 self .assertTrue (authority .has_valid_issuer (),
702702 "Issuer ending with ciamlogin.com should be valid" )
703703
704+ # Domain spoofing prevention tests
705+ @patch ("msal.authority.tenant_discovery" )
706+ def test_spoofed_b2c_host_should_be_rejected (self , tenant_discovery_mock ):
707+ """fakeb2clogin.com must NOT match b2clogin.com"""
708+ authority_url = "https://custom-domain.com/tenant"
709+ issuer = "https://fakeb2clogin.com/tenant"
710+ tenant_discovery_mock .return_value = {
711+ "authorization_endpoint" : "https://example.com/oauth2/authorize" ,
712+ "token_endpoint" : "https://example.com/oauth2/token" ,
713+ "issuer" : issuer ,
714+ }
715+ with self .assertRaises (ValueError ):
716+ Authority (None , self .http_client , oidc_authority_url = authority_url )
717+
718+ @patch ("msal.authority.tenant_discovery" )
719+ def test_spoofed_b2c_host_with_prefix_should_be_rejected (self , tenant_discovery_mock ):
720+ """evilb2clogin.com must NOT match b2clogin.com"""
721+ authority_url = "https://custom-domain.com/tenant"
722+ issuer = "https://evilb2clogin.com/tenant"
723+ tenant_discovery_mock .return_value = {
724+ "authorization_endpoint" : "https://example.com/oauth2/authorize" ,
725+ "token_endpoint" : "https://example.com/oauth2/token" ,
726+ "issuer" : issuer ,
727+ }
728+ with self .assertRaises (ValueError ):
729+ Authority (None , self .http_client , oidc_authority_url = authority_url )
730+
731+ @patch ("msal.authority.tenant_discovery" )
732+ def test_b2c_domain_used_as_subdomain_of_evil_site_should_be_rejected (self , tenant_discovery_mock ):
733+ """b2clogin.com.evil.com must NOT match b2clogin.com"""
734+ authority_url = "https://custom-domain.com/tenant"
735+ issuer = "https://b2clogin.com.evil.com/tenant"
736+ tenant_discovery_mock .return_value = {
737+ "authorization_endpoint" : "https://example.com/oauth2/authorize" ,
738+ "token_endpoint" : "https://example.com/oauth2/token" ,
739+ "issuer" : issuer ,
740+ }
741+ with self .assertRaises (ValueError ):
742+ Authority (None , self .http_client , oidc_authority_url = authority_url )
743+
744+ @patch ("msal.authority.tenant_discovery" )
745+ def test_spoofed_ciamlogin_host_should_be_rejected (self , tenant_discovery_mock ):
746+ """fakeciamlogin.com must NOT match ciamlogin.com"""
747+ authority_url = "https://custom-domain.com/tenant"
748+ issuer = "https://fakeciamlogin.com/tenant"
749+ tenant_discovery_mock .return_value = {
750+ "authorization_endpoint" : "https://example.com/oauth2/authorize" ,
751+ "token_endpoint" : "https://example.com/oauth2/token" ,
752+ "issuer" : issuer ,
753+ }
754+ with self .assertRaises (ValueError ):
755+ Authority (None , self .http_client , oidc_authority_url = authority_url )
756+
757+ @patch ("msal.authority.tenant_discovery" )
758+ def test_valid_b2c_subdomain_should_be_accepted (self , tenant_discovery_mock ):
759+ """login.b2clogin.com should match .b2clogin.com"""
760+ authority_url = "https://custom-domain.com/tenant"
761+ issuer = "https://login.b2clogin.com/tenant"
762+ authority = self ._create_authority_with_issuer (authority_url , issuer , tenant_discovery_mock )
763+ self .assertTrue (authority .has_valid_issuer (),
764+ "Legitimate subdomain of b2clogin.com should be valid" )
765+
0 commit comments