{"record":{"id":"a2e067c287f73e3d","repo":"crewAIInc/crewAI","slug":"either-jwks-url-or-introspection-url-must-be-provi","errorCode":null,"errorMessage":"Either jwks_url or introspection_url must be provided for token validation","messagePattern":"Either jwks_url or introspection_url must be provided for token validation","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"lib/crewai/src/crewai/a2a/auth/server_schemes.py","lineNumber":451,"sourceCode":"    )\n    jwks_cache_ttl: int = Field(\n        default=3600,\n        description=\"TTL for JWKS cache in seconds\",\n        ge=60,\n    )\n    clock_skew_seconds: float = Field(\n        default=30.0,\n        description=\"Allowed clock skew for token validation\",\n        ge=0.0,\n    )\n\n    _jwk_client: PyJWKClient | None = PrivateAttr(default=None)\n\n    @model_validator(mode=\"after\")\n    def _validate_and_init(self) -> Self:\n        \"\"\"Validate configuration and initialize JWKS client if needed.\"\"\"\n        if not self.jwks_url and not self.introspection_url:\n            raise ValueError(\n                \"Either jwks_url or introspection_url must be provided for token validation\"\n            )\n\n        if self.introspection_url:\n            if not self.introspection_client_id or not self.introspection_client_secret:\n                raise ValueError(\n                    \"introspection_client_id and introspection_client_secret are required \"\n                    \"when using token introspection\"\n                )\n\n        if self.jwks_url:\n            self._jwk_client = PyJWKClient(\n                str(self.jwks_url), lifespan=self.jwks_cache_ttl\n            )\n\n        return self\n\n    async def authenticate(self, token: str) -> AuthenticatedUser:","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai/src/crewai/a2a/auth/server_schemes.py#L433-L469","documentation":"A pydantic ValidationError raised inside OAuth2ServerAuth's model_validator(mode='after') when the configuration provides neither jwks_url nor introspection_url. The scheme refuses to construct because it would have no way to validate tokens. It surfaces at configuration time (startup), not per request.","triggerScenarios":"Instantiating OAuth2ServerAuth() with only issuer/audience fields and no validation endpoint: OAuth2ServerAuth(issuer='https://idp', audience='api') raises immediately; or a config dict built from env vars where both JWKS_URL and INTROSPECTION_URL are empty strings.","commonSituations":"Copy-pasting an OIDCAuth config into OAuth2ServerAuth and dropping jwks_url; env-var-driven configs where the variable name changed between versions; partially filled YAML config files.","solutions":["Provide at least one validation endpoint: OAuth2ServerAuth(jwks_url='https://idp/.well-known/jwks.json', ...) for JWTs.","For opaque tokens, provide introspection_url plus introspection_client_id and introspection_client_secret.","Validate the config at startup and log which fields are missing instead of discovering it via the traceback.","If both are configured, ensure at least one URL is reachable before going live."],"exampleFix":"# before\nauth = OAuth2ServerAuth(issuer=\"https://idp\", audience=\"api\")  # ValidationError\n\n# after\nauth = OAuth2ServerAuth(\n    issuer=\"https://idp\", audience=\"api\",\n    jwks_url=\"https://idp/.well-known/jwks.json\",\n)","handlingStrategy":"validation","validationCode":"from crewai.a2a.auth.server_schemes import OAuth2ServerAuth\n\n# fails fast with a clear pydantic error instead of at request time\nauth = OAuth2ServerAuth(\n    jwks_url=\"https://idp/.well-known/jwks.json\",  # or introspection_url + credentials\n)","typeGuard":null,"tryCatchPattern":"try:\n    auth = OAuth2ServerAuth(**cfg)\nexcept ValidationError as e:\n    if \"jwks_url or introspection_url\" in str(e):\n        cfg.setdefault(\"jwks_url\", os.environ[\"JWKS_URL\"])  # supply the missing endpoint\n        auth = OAuth2ServerAuth(**cfg)","preventionTips":["Always pass exactly one of jwks_url (JWTs) or introspection_url (opaque tokens).","Validate auth config during startup, not lazily on first request.","Use pydantic model_validate on config dicts to surface all errors at once."],"tags":["a2a","oauth2","configuration","pydantic","validation"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}