fix: reject empty/OIDC-only required_scopes in AzureProvider (#3503)

🤖 Generated with Claude Code
This commit is contained in:
Jeremiah Lowin 2026-03-14 16:35:43 -04:00 committed by GitHub
commit 71ba030380
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 62 additions and 23 deletions

View file

@ -195,15 +195,17 @@ class AzureProvider(OAuthProxy):
# NOT standard OIDC scopes (openid, profile, email, offline_access).
# Filter out OIDC scopes from validation - they'll still be sent to Azure
# during authorization (handled by _prefix_scopes_for_azure).
if parsed_required_scopes:
validation_scopes = [
s for s in parsed_required_scopes if s not in OIDC_SCOPES
]
# If all scopes were OIDC scopes, use None (no scope validation)
if not validation_scopes:
validation_scopes = None
else:
validation_scopes = None
validation_scopes = [
s for s in (parsed_required_scopes or []) if s not in OIDC_SCOPES
]
if not validation_scopes:
raise ValueError(
"AzureProvider requires at least one non-OIDC scope in "
"required_scopes (e.g., 'read', 'write'). OIDC scopes like "
"'openid', 'profile', 'email', and 'offline_access' are not "
"included in Azure access token claims and cannot be used for "
"scope enforcement."
)
token_verifier = JWTVerifier(
jwks_uri=jwks_uri,

View file

@ -147,23 +147,60 @@ class TestOIDCScopeHandling:
# Token validator should only require non-OIDC scopes
assert provider._token_validator.required_scopes == ["read"]
def test_required_scopes_all_oidc_results_in_no_validation(
def test_required_scopes_all_oidc_raises_value_error(
self, memory_storage: MemoryStore
):
"""Test that if all required_scopes are OIDC, no scope validation occurs."""
provider = AzureProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="test-tenant",
base_url="https://myserver.com",
identifier_uri="api://my-api",
required_scopes=["openid", "profile"],
jwt_signing_key="test-secret",
client_storage=memory_storage,
)
"""Test that providing only OIDC scopes raises ValueError."""
with pytest.raises(ValueError, match="at least one non-OIDC scope"):
AzureProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="test-tenant",
base_url="https://myserver.com",
identifier_uri="api://my-api",
required_scopes=["openid", "profile"],
jwt_signing_key="test-secret",
client_storage=memory_storage,
)
# Token validator should have empty required scopes (all were OIDC)
assert provider._token_validator.required_scopes == []
def test_empty_required_scopes_raises_value_error(
self, memory_storage: MemoryStore
):
"""Test that providing empty required_scopes raises ValueError."""
with pytest.raises(ValueError, match="at least one non-OIDC scope"):
AzureProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="test-tenant",
base_url="https://myserver.com",
identifier_uri="api://my-api",
required_scopes=[],
jwt_signing_key="test-secret",
client_storage=memory_storage,
)
@pytest.mark.parametrize(
"scopes",
[
["offline_access"],
["openid", "email", "profile", "offline_access"],
["email"],
],
)
def test_only_oidc_scopes_raises_value_error(
self, memory_storage: MemoryStore, scopes: list[str]
):
"""Test that various OIDC-only scope combinations raise ValueError."""
with pytest.raises(ValueError, match="at least one non-OIDC scope"):
AzureProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="test-tenant",
base_url="https://myserver.com",
required_scopes=scopes,
jwt_signing_key="test-secret",
client_storage=memory_storage,
)
def test_valid_scopes_includes_oidc_scopes(self, memory_storage: MemoryStore):
"""Test that valid_scopes advertises OIDC scopes to clients."""