{"record":{"id":"9bbcba257bdab6ab","repo":"PrefectHQ/fastmcp","slug":"oidc-discovery-for-issuer-issuer-r-recently-fail","errorCode":null,"errorMessage":"OIDC discovery for issuer {issuer!r} recently failed; backing off","messagePattern":"OIDC discovery for issuer (.+?) recently failed; backing off","errorType":"exception","errorClass":"IdentityAssertionError","httpStatus":null,"severity":"warning","filePath":"fastmcp_slim/fastmcp/server/auth/identity_assertion.py","lineNumber":273,"sourceCode":"            self._cleanup_expired_jtis()\n            self._last_cleanup = now\n\n    async def _discover_jwks_uri(self, issuer: str) -> str:\n        \"\"\"Discover an issuer's JWKS URI via OIDC discovery.\n\n        Fetches ``{issuer}/.well-known/openid-configuration`` and returns its\n        ``jwks_uri``. Trusted issuers are operator-configured, so this uses a\n        plain fetch (consistent with how operator-configured JWKS URIs are\n        treated elsewhere, including localhost issuers in development).\n        \"\"\"\n        lock = self._discovery_locks.setdefault(issuer, asyncio.Lock())\n        async with lock:\n            failed_at = self._discovery_failures.get(issuer)\n            if (\n                failed_at is not None\n                and time.monotonic() - failed_at < self._discovery_failure_cooldown\n            ):\n                raise IdentityAssertionError(\n                    f\"OIDC discovery for issuer {issuer!r} recently failed; backing off\"\n                )\n            return await self._fetch_discovery(issuer)\n\n    async def _fetch_discovery(self, issuer: str) -> str:\n        \"\"\"Perform the actual discovery fetch; caller holds the issuer lock.\"\"\"\n        config_url = issuer.rstrip(\"/\") + \"/.well-known/openid-configuration\"\n        try:\n            async with httpx2.AsyncClient() as client:\n                response = await client.get(config_url, timeout=10.0)\n                response.raise_for_status()\n                body = response.json()\n        except (httpx2.HTTPError, ValueError) as e:\n            self._discovery_failures[issuer] = time.monotonic()\n            raise IdentityAssertionError(\n                f\"OIDC discovery for issuer {issuer!r} failed: {e}\"\n            ) from e\n        if not isinstance(body, dict):","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/identity_assertion.py#L255-L291","documentation":"OIDC discovery (.well-known/openid-configuration) for a trusted issuer failed recently, and the manager enforces a cooldown per issuer before retrying. Within that window `_discover_jwks_uri` raises `IdentityAssertionError` immediately instead of re-fetching, protecting the authorization server from repeated slow/timing-out discovery requests.","triggerScenarios":"`_get_verifier` (via `_discover_jwks_uri`) is called for an issuer whose previous discovery fetch failed within `_discovery_failure_cooldown` seconds — e.g. the issuer's discovery endpoint was down or timed out moments earlier and another token exchange arrives.","commonSituations":"Issuer's well-known endpoint temporarily unreachable (network outage, DNS failure, TLS problems); issuer misconfigured in trusted_issuers (wrong URL, no discovery document); rate limiting or firewall blocking the server's outbound requests; flaky issuer during a deploy window.","solutions":["Wait for the cooldown window to pass and retry — discovery will be attempted again automatically","Verify the issuer URL is correct and that `https://<issuer>/.well-known/openid-configuration` is reachable from the server","Fix network egress/DNS/TLS issues between your server and the issuer (proxy settings, firewall rules)","If the issuer has no discovery document, configure its JWKS URI statically instead of relying on discovery"],"exampleFix":"// before (issuer unreachable / typo)\ntrusted_issuers=[\"https://issuer.example.co\"]\n// after\ntype: curl https://issuer.example.com/.well-known/openid-configuration  # verify then use\ntrusted_issuers=[\"https://issuer.example.com\"]","handlingStrategy":"retry","validationCode":"import httpx\nr = httpx.get(f\"{issuer.rstrip('/')}/.well-known/openid-configuration\", timeout=5)\nr.raise_for_status()\nassert \"jwks_uri\" in r.json(), \"discovery doc missing jwks_uri\"","typeGuard":"def issuer_discoverable(issuer: str) -> bool:\n    try:\n        r = httpx.get(f\"{issuer.rstrip('/')}/.well-known/openid-configuration\", timeout=5)\n        return r.ok and \"jwks_uri\" in r.json()\n    except httpx.HTTPError:\n        return False","tryCatchPattern":"try:\n    verifier = await manager._get_verifier(issuer)\nexcept IdentityAssertionError as e:\n    if \"backing off\" in str(e):\n        await asyncio.sleep(manager._discovery_failure_cooldown)\n        verifier = await manager._get_verifier(issuer)\n    else:\n        raise","preventionTips":["Verify each trusted issuer's well-known endpoint is reachable from the server before adding it","Monitor outbound connectivity/DNS/TLS to issuer domains","Configure the JWKS URI statically for issuers without discovery support","Treat the cooldown as a signal to fix the underlying issuer reachability, not just to retry faster"],"tags":["network","oidc","discovery","retry","backoff"],"backgroundTag":"oidc-discovery-failure","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}