PrefectHQ/fastmcp · error · NotImplementedError

Azure AD B2C does not support the On-Behalf-Of (OBO) flow. U

Error message

Azure AD B2C does not support the On-Behalf-Of (OBO) flow. Use AzureProvider with standard Entra ID for OBO scenarios.

What it means

get_obo_credential() performs the OAuth On-Behalf-Of flow to exchange a user token for a downstream-service token. Azure AD B2C does not implement the OBO flow at all, so when the provider is a B2C variant (_obo_supported is False) the method raises NotImplementedError instead of attempting a doomed exchange.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/providers/azure.py:647

    async def get_obo_credential(self, user_assertion: str) -> OnBehalfOfCredential:
        """Get a cached or new OnBehalfOfCredential for OBO token exchange.

        Credentials are cached by user assertion so the Azure SDK's internal
        token cache can avoid redundant OBO exchanges when the same user
        calls multiple tools with the same scopes.

        Args:
            user_assertion: The user's access token to exchange via OBO.

        Returns:
            A configured OnBehalfOfCredential ready for get_token() calls.

        Raises:
            NotImplementedError: If OBO is not supported (e.g. Azure AD B2C).
            ImportError: If azure-identity is not installed (requires fastmcp[azure]).
        """
        if not self._obo_supported:
            raise NotImplementedError(
                "Azure AD B2C does not support the On-Behalf-Of (OBO) flow. "
                "Use AzureProvider with standard Entra ID for OBO scenarios."
            )
        _require_azure_identity("OBO token exchange")
        from azure.identity.aio import OnBehalfOfCredential

        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}",
        }

View on GitHub (pinned to 1f02114297)

Solutions

  1. Migrate the server to standard Entra ID (plain AzureProvider) if OBO is a requirement.
  2. Handle NotImplementedError and fall back to client-credentials auth for downstream calls (service identity instead of delegated user identity).
  3. Alternatively call downstream APIs directly with the user's original access token if the downstream API accepts the same B2C issuer.

Example fix

// before
token = await provider.get_obo_credential(user_assertion=user_token)
// after
try:
    token = await provider.get_obo_credential(user_assertion=user_token)
except NotImplementedError:
    token = client_credentials_token_for_downstream()  # B2C has no OBO
Defensive patterns

Strategy: try-catch

Validate before calling

def obo_supported(provider) -> bool:
    return bool(getattr(provider, "_obo_supported", True))

Type guard

def is_b2c_provider(provider) -> bool:
    return isinstance(provider, AzureProvider) and not getattr(provider, "_obo_supported", True)

Try / catch

try:
    cred = await provider.get_obo_credential(user_assertion=token)
except NotImplementedError:
    cred = await get_client_credentials_token(scopes)  # fallback path

Prevention

When it happens

Trigger: Calling await b2c_provider.get_obo_credential(user_assertion=...) on an AzureProvider created via from_b2c, or using EntraOBOToken as an async context manager against a B2C-backed provider.

Common situations: Building a multi-service architecture on B2C and trying to call downstream APIs with delegated user identity; porting Entra ID OBO code to a B2C tenant.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/95f895e83dd470e0. Report an issue: GitHub.