PrefectHQ/fastmcp · error · ValueError
OBO token exchange requires either a client_secret or a subc
Error message
OBO token exchange requires either a client_secret or a subclass that overrides get_obo_credential() to provide alternative credentials (e.g., client_assertion_func for managed identity).
What it means
The OBO flow requires a confidential client credential to authorize the exchange. AzureProvider's get_obo_credential builds OnBehalfOfCredential kwargs; if no upstream client_secret was configured (and no subclass supplies alternative credentials like a client assertion), the exchange cannot be authenticated, so the method raises ValueError.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/providers/azure.py:671
key = hashlib.sha256(user_assertion.encode()).hexdigest()
if key in self._obo_credentials:
self._obo_credentials.move_to_end(key)
return self._obo_credentials[key]
obo_kwargs: dict[str, Any] = {
"tenant_id": self._tenant_id,
"client_id": self._upstream_client_id,
"user_assertion": user_assertion,
"authority": f"https://{self._base_authority}",
}
if self._upstream_client_secret is not None:
obo_kwargs["client_secret"] = (
self._upstream_client_secret.get_secret_value()
)
else:
raise ValueError(
"OBO token exchange requires either a client_secret or a subclass "
"that overrides get_obo_credential() to provide alternative credentials "
"(e.g., client_assertion_func for managed identity)."
)
credential = OnBehalfOfCredential(**obo_kwargs)
self._obo_credentials[key] = credential
# Evict oldest if over capacity
while len(self._obo_credentials) > self._obo_max_credentials:
_, evicted = self._obo_credentials.popitem(last=False)
await evicted.close()
return credential
async def close_obo_credentials(self) -> None:
"""Close all cached OBO credentials."""
credentials = list(self._obo_credentials.values())
self._obo_credentials.clear()View on GitHub (pinned to 1f02114297)
Solutions
- Configure the upstream client secret on the AzureProvider so _upstream_client_secret is set.
- Subclass AzureProvider and override get_obo_credential() to supply alternative credentials such as client_assertion_func (certificate or managed-identity-based assertion).
- If no secret can be stored, use certificate credential via azure-identity's OnBehalfOfCredential(client_certificate=...) in your override.
Example fix
// before
provider = AzureProvider(client_id=cid, tenant_id=tid) # no secret -> OBO fails
// after
class MyProvider(AzureProvider):
async def get_obo_credential(self, *, user_assertion, scopes):
... # supply client_assertion_func-based OnBehalfOfCredential
provider = MyProvider(client_id=cid, tenant_id=tid, client_secret=secret) Defensive patterns
Strategy: validation
Validate before calling
if getattr(provider, "_upstream_client_secret", None) is None and type(provider).get_obo_credential is AzureProvider.get_obo_credential:
raise ValueError("Configure client_secret or override get_obo_credential before OBO") Type guard
def can_do_obo(provider) -> bool:
return getattr(provider, "_upstream_client_secret", None) is not None or type(provider).get_obo_credential is not AzureProvider.get_obo_credential Try / catch
try:
cred = await provider.get_obo_credential(user_assertion=token)
except ValueError as e:
if "client_secret" in str(e):
logger.error("Configure upstream client secret or a get_obo_credential override")
raise Prevention
- Wire the upstream client secret via secret manager at provider construction
- Prefer subclass override with client_assertion_func where secrets can't be stored
- Fail fast at startup by probing OBO readiness before serving traffic
When it happens
Trigger: Calling get_obo_credential() on an AzureProvider (Entra, OBO-capable) constructed without an upstream client secret — e.g. AzureProvider created with public-client settings or with the upstream client secret omitted.
Common situations: Setting up OBO in an environment that intentionally avoids stored secrets and forgetting to supply client_assertion_func via a subclass; constructing AzureProvider from a config file that omits the client secret.
Related errors
- EntraOBOToken requires an AzureProvider as the auth provider
- AzureProvider requires at least one non-OIDC scope in requir
- tenant_name should be the short name without the .onmicrosof
- Azure AD B2C does not support the On-Behalf-Of (OBO) flow. U
- No access token available. Cannot perform OBO exchange.
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/152c1dd341d57522.
Report an issue: GitHub.