Skip to content

Commit c04af8d

Browse files
committed
added a additional check for domain checking in b2c
1 parent 1f71ede commit c04af8d

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

msal/authority.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def has_valid_issuer(self):
251251
return True
252252

253253
# Case 5: Check if issuer host ends with any well-known B2C host (e.g., tenant.b2clogin.com)
254-
if any(issuer_host.endswith(h) for h in WELL_KNOWN_B2C_HOSTS):
254+
if any(issuer_host.endswith("." + h) for h in WELL_KNOWN_B2C_HOSTS):
255255
return True
256256

257257
return False

tests/test_authority.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)