{"record":{"id":"1245d3173487188c","repo":"infiniflow/ragflow","slug":"failed-to-fetch-oidc-metadata-e","errorCode":null,"errorMessage":"Failed to fetch OIDC metadata: {e}","messagePattern":"Failed to fetch OIDC metadata: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"api/apps/auth/oidc.py","lineNumber":110,"sourceCode":"        self.jwks_uri = config[\"jwks_uri\"]\n        # Pin the accepted ID-token signing algorithms at construction time\n        # from a trusted source (provider metadata + safe allowlist) so the\n        # JWT verification step in :meth:`parse_id_token` cannot be tricked\n        # by attacker-controlled JWT headers (CWE-345 / CWE-347).\n        self.id_token_signing_algs = _resolve_id_token_signing_algs(oidc_metadata)\n\n    @staticmethod\n    def _load_oidc_metadata(issuer):\n        \"\"\"\n        Load OIDC metadata from `/.well-known/openid-configuration`.\n        \"\"\"\n        try:\n            metadata_url = f\"{issuer}/.well-known/openid-configuration\"\n            response = sync_request(\"GET\", metadata_url, timeout=7)\n            response.raise_for_status()\n            return response.json()\n        except Exception as e:\n            raise ValueError(f\"Failed to fetch OIDC metadata: {e}\")\n\n    def parse_id_token(self, id_token):\n        \"\"\"\n        Parse and validate OIDC ID Token (JWT format) with signature verification.\n\n        The accepted signing algorithms come from ``self.id_token_signing_algs``\n        (pinned at construction time from the provider's discovery metadata,\n        intersected with :data:`_ALLOWED_OIDC_SIGNING_ALGS`). We deliberately\n        do **not** read the algorithm from the unverified JWT header — doing\n        so would let an attacker bypass signature verification by setting\n        ``\"alg\": \"none\"`` or pull off the classic RSA / HMAC algorithm\n        confusion by setting ``\"alg\": \"HS256\"`` and signing with the public\n        key fetched from the provider's JWKS (CWE-345 / CWE-347).\n        \"\"\"\n        try:\n            # Use PyJWT's PyJWKClient to fetch JWKS and find signing key.\n            # The client reads the ``kid`` from the JWT header internally to\n            # look up the key — that's fine: ``kid`` is not a security","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/api/apps/auth/oidc.py#L92-L128","documentation":"OIDCClient._load_oidc_metadata GETs {issuer}/.well-known/openid-configuration with a 7-second timeout and wraps every failure in ValueError('Failed to fetch OIDC metadata: {e}') (api/apps/auth/oidc.py:110). The nested message distinguishes HTTP errors, timeouts, DNS/connection failures, and JSON parse errors.","triggerScenarios":"Issuer URL wrong (typo, wrong port, path with or without trailing slash mismatch vs the IdP's real issuer value); RAGFlow server has no outbound network/DNS to the IdP; TLS certificate invalid; issuer behind a proxy that blocks the well-known path; IdP slow enough to exceed the fixed 7s timeout; response is HTML (auth wall) so .json() raises.","commonSituations":"Containerized deployments without DNS or egress rules for the IdP host; issuer copied with /auth suffix mismatch (e.g. Keycloak realm URL missing '/realms/<name>'); self-signed certs without a trusted CA; trailing-slash discrepancies producing 404.","solutions":["From the RAGFlow server (inside the container), curl the exact URL: {issuer}/.well-known/openid-configuration - it must return the discovery JSON.","Fix the issuer to the IdP's exact base: for Keycloak that is https://<host>/realms/<realm>; drop any /.well-known/... suffix you may have copied.","Resolve network issues: DNS, egress firewall, proxy env vars, and trust the IdP's CA on the host running RAGFlow.","If the IdP is slow, note the timeout is hardcoded at 7s - reduce IdP latency or serve discovery from a faster endpoint."],"exampleFix":"# verify discovery from inside the container\ndocker exec ragflow-server curl -sS \\\n  'https://sso.example.com/realms/main/.well-known/openid-configuration'\n# must print JSON containing issuer, jwks_uri, authorization_endpoint, ...","handlingStrategy":"retry","validationCode":"import requests\n\ndef discovery_reachable(issuer, timeout=7):\n    try:\n        r = requests.get(f\"{issuer}/.well-known/openid-configuration\", timeout=timeout)\n        return r.ok and isinstance(r.json(), dict) and \"issuer\" in r.json()\n    except Exception:\n        return False\n\n# run at provider-config save time and fail fast","typeGuard":null,"tryCatchPattern":"for attempt in range(3):\n    try:\n        client = OIDCClient(config)\n        break\n    except ValueError as e:\n        if \"Failed to fetch OIDC metadata\" in str(e) and \"timed out\" in str(e).lower() and attempt < 2:\n            continue  # transient timeout - retry\n        raise ConfigError(f\"OIDC discovery failed: {e}\") from e","preventionTips":["Probe the well-known URL from inside the RAGFlow container when saving the provider config.","Match the issuer byte-for-byte with the IdP's advertised issuer (mind trailing slashes, realm paths).","Ensure DNS/egress and CA trust to the IdP in the deployment environment.","Remember discovery has a fixed 7s timeout - keep IdP latency below it or cache discovery."],"tags":["auth","oidc","network","discovery","configuration"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}