crewAIInc/crewAI · critical · HTTPException

OAuth2 introspection not configured

Error message

OAuth2 introspection not configured

What it means

Raised by OAuth2ServerAuth._authenticate_introspection() when it is invoked but introspection_url is empty. It maps to HTTP 500: the internal state (routing to introspection without an introspection endpoint) contradicts the model validator's guarantee, so in practice it indicates an object that bypassed normal construction or was mutated after validation.

Source

Thrown at lib/crewai/src/crewai/a2a/auth/server_schemes.py:582

            raise HTTPException(
                status_code=HTTP_503_SERVICE_UNAVAILABLE,
                detail="Unable to fetch signing keys",
            ) from None
        except jwt.InvalidTokenError as e:
            logger.debug(
                "OAuth2 authentication failed",
                extra={"reason": "invalid_token", "error": str(e), "scheme": "oauth2"},
            )
            raise HTTPException(
                status_code=HTTP_401_UNAUTHORIZED,
                detail="Invalid or missing authentication credentials",
            ) from None

    async def _authenticate_introspection(self, token: str) -> AuthenticatedUser:
        """Authenticate using OAuth2 token introspection (RFC 7662)."""

        if not self.introspection_url:
            raise HTTPException(
                status_code=HTTP_500_INTERNAL_SERVER_ERROR,
                detail="OAuth2 introspection not configured",
            )

        try:
            async with httpx.AsyncClient() as client:
                response = await client.post(
                    str(self.introspection_url),
                    data={"token": token},
                    auth=(
                        self.introspection_client_id or "",
                        self.introspection_client_secret.get_secret_value()
                        if self.introspection_client_secret
                        else "",
                    ),
                )
                response.raise_for_status()
                introspection_result = response.json()

View on GitHub (pinned to 754d7323be)

Solutions

  1. Construct OAuth2ServerAuth normally with introspection_url so validation guarantees consistency.
  2. Avoid model_construct() and in-place mutation of auth scheme objects; rebuild them instead.
  3. Add a startup sanity check: the scheme must have _jwk_client or a non-empty introspection_url.
  4. If deserializing from config, round-trip through OAuth2ServerAuth.model_validate().

Example fix

# before
auth = OAuth2ServerAuth.model_construct()  # no endpoints; introspection path -> 500

# after
auth = OAuth2ServerAuth(
    introspection_url="https://idp/oauth/introspect",
    introspection_client_id="svc",
    introspection_client_secret="***",
)
Defensive patterns

Strategy: validation

Validate before calling

from crewai.a2a.auth.server_schemes import OAuth2ServerAuth

auth = OAuth2ServerAuth(
    introspection_url="https://idp/oauth/introspect",
    introspection_client_id="svc",
    introspection_client_secret="***",
)
assert auth.introspection_url, "introspection must be configured when used"

Type guard

def is_introspection_ready(scheme) -> bool:
    """True when the scheme can call an introspection endpoint."""
    return bool(getattr(scheme, "introspection_url", None))

Prevention

When it happens

Trigger: authenticate() dispatches to _authenticate_introspection() because _jwk_client is None, yet introspection_url was also cleared — only reachable via model_construct() or manual attribute surgery, since a validly constructed scheme has at least one endpoint.

Common situations: Tests that build schemes with model_construct(); code that reassigns config fields post-construction; framework code deserializing schemes without running validators.

Related errors


AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15). Data as JSON: /api/errors/8440d20f3bd49bb5. Report an issue: GitHub.