Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions openrag/components/auth/test_oidc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,52 @@ async def test_nonce_mismatch_raises(self, client):
with pytest.raises(ValueError, match="nonce"):
await client.exchange_code(code="code", code_verifier="v", expected_nonce="wrong-nonce")

# OIDC Core 1.0 §3.1.3.7: when aud lists multiple audiences, azp MUST
# be present and equal to client_id. Regression for #385.
@pytest.mark.asyncio
async def test_multi_aud_requires_matching_azp(self, client):
_setup_discovery(client._mock_router)
_setup_jwks(client._mock_router)

nonce = "n-azp"
# Multi-aud token with the wrong (or missing) azp must be rejected
id_token = _sign_jwt(
_id_token_payload(nonce, extra={"aud": [CLIENT_ID, "other-client"], "azp": "other-client"})
)
token_response = {
"id_token": id_token,
"access_token": "at",
"expires_in": 300,
"token_type": "Bearer",
}
client._mock_router.post(f"{ISSUER}/protocol/openid-connect/token").mock(
return_value=httpx.Response(200, json=token_response)
)

with pytest.raises(ValueError, match="multi-aud"):
await client.exchange_code(code="code", code_verifier="v", expected_nonce=nonce)

@pytest.mark.asyncio
async def test_multi_aud_with_correct_azp_passes(self, client):
_setup_discovery(client._mock_router)
_setup_jwks(client._mock_router)

nonce = "n-azp-ok"
id_token = _sign_jwt(_id_token_payload(nonce, extra={"aud": [CLIENT_ID, "other-client"], "azp": CLIENT_ID}))
token_response = {
"id_token": id_token,
"access_token": "at",
"expires_in": 300,
"token_type": "Bearer",
}
client._mock_router.post(f"{ISSUER}/protocol/openid-connect/token").mock(
return_value=httpx.Response(200, json=token_response)
)

bundle = await client.exchange_code(code="code", code_verifier="v", expected_nonce=nonce)
assert bundle.claims["aud"] == [CLIENT_ID, "other-client"]
assert bundle.claims["azp"] == CLIENT_ID


# ---------------------------------------------------------------------------
# Token refresh
Expand Down
6 changes: 6 additions & 0 deletions openrag/services/auth/oidc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,12 @@ async def _verify_id_token(self, token: str, *, expected_nonce: str | None) -> d
if isinstance(aud, list):
if self.client_id not in aud:
raise ValueError(f"ID token aud {aud!r} does not contain client_id {self.client_id!r}")
# OIDC Core 1.0 §3.1.3.7: when aud lists multiple audiences, azp
# MUST be present and equal to client_id.
if len(aud) > 1:
azp = decoded.get("azp")
if azp != self.client_id:
raise ValueError(f"ID token has multi-aud {aud!r} but azp {azp!r} != client_id {self.client_id!r}")
elif aud != self.client_id:
raise ValueError(f"ID token aud {aud!r} != client_id {self.client_id!r}")

Expand Down
Loading