crewAIInc/crewAI · critical · HTTPException

OAuth2 JWKS not initialized

Error message

OAuth2 JWKS not initialized

What it means

Raised by OAuth2ServerAuth._authenticate_jwt() when _jwk_client is None, i.e. the scheme was told to validate JWTs (introspection_url absent or authenticate() routed to _authenticate_jwt) but the JWKS client was never built. It maps to HTTP 500: an internal misconfiguration, not a client fault.

Source

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

        to token introspection.

        Args:
            token: The OAuth2 access token to authenticate.

        Returns:
            AuthenticatedUser on successful authentication.

        Raises:
            HTTPException: If authentication fails.
        """
        if self._jwk_client:
            return await self._authenticate_jwt(token)
        return await self._authenticate_introspection(token)

    async def _authenticate_jwt(self, token: str) -> AuthenticatedUser:
        """Authenticate using JWKS JWT validation."""
        if self._jwk_client is None:
            raise HTTPException(
                status_code=HTTP_500_INTERNAL_SERVER_ERROR,
                detail="OAuth2 JWKS not initialized",
            )

        try:
            signing_key = self._jwk_client.get_signing_key_from_jwt(token)

            decode_options: Options = {
                "require": self.required_claims,
            }

            claims = jwt.decode(
                token,
                signing_key.key,
                algorithms=self.algorithms,
                audience=self.audience,
                issuer=self.issuer,
                leeway=self.clock_skew_seconds,

View on GitHub (pinned to 754d7323be)

Solutions

  1. Construct OAuth2ServerAuth normally with jwks_url so the validator initializes _jwk_client.
  2. Do not use model_construct() or mutate private attrs; rebuild the scheme when config changes.
  3. Add a startup smoke check: for a JWT-based scheme, assert scheme._jwk_client is not None.
  4. Recreate the scheme object rather than patching fields after initialization.

Example fix

# before
auth = OAuth2ServerAuth.model_construct(issuer="https://idp")  # no jwks client

# after
auth = OAuth2ServerAuth(
    issuer="https://idp", audience="api",
    jwks_url="https://idp/.well-known/jwks.json",
)
Defensive patterns

Strategy: validation

Validate before calling

from crewai.a2a.auth.server_schemes import OAuth2ServerAuth

auth = OAuth2ServerAuth(jwks_url="https://idp/.well-known/jwks.json", ...)
assert auth._jwk_client is not None, "JWKS client must initialize at construction"

Type guard

def is_jwt_ready(scheme) -> bool:
    """True when the scheme can validate JWTs (has a live JWKS client)."""
    return getattr(scheme, "_jwk_client", None) is not None

Prevention

When it happens

Trigger: Auth flow reaches _authenticate_jwt() with jwks_url unset — practically only possible when the object bypassed validation (model_construct) or _jwk_client was cleared, because the model validator requires at least one endpoint and builds the client when jwks_url is present.

Common situations: Objects built with model_construct() or copied/reconstructed in a way that skips the model validator; monkeypatched tests that null out _jwk_client; code that swaps config attributes after construction.

Related errors


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