infiniflow/ragflow · error · ValueError
Failed to fetch OIDC metadata: {e}
Error message
Failed to fetch OIDC metadata: {e} What it means
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.
Source
Thrown at api/apps/auth/oidc.py:110
self.jwks_uri = config["jwks_uri"]
# Pin the accepted ID-token signing algorithms at construction time
# from a trusted source (provider metadata + safe allowlist) so the
# JWT verification step in :meth:`parse_id_token` cannot be tricked
# by attacker-controlled JWT headers (CWE-345 / CWE-347).
self.id_token_signing_algs = _resolve_id_token_signing_algs(oidc_metadata)
@staticmethod
def _load_oidc_metadata(issuer):
"""
Load OIDC metadata from `/.well-known/openid-configuration`.
"""
try:
metadata_url = f"{issuer}/.well-known/openid-configuration"
response = sync_request("GET", metadata_url, timeout=7)
response.raise_for_status()
return response.json()
except Exception as e:
raise ValueError(f"Failed to fetch OIDC metadata: {e}")
def parse_id_token(self, id_token):
"""
Parse and validate OIDC ID Token (JWT format) with signature verification.
The accepted signing algorithms come from ``self.id_token_signing_algs``
(pinned at construction time from the provider's discovery metadata,
intersected with :data:`_ALLOWED_OIDC_SIGNING_ALGS`). We deliberately
do **not** read the algorithm from the unverified JWT header — doing
so would let an attacker bypass signature verification by setting
``"alg": "none"`` or pull off the classic RSA / HMAC algorithm
confusion by setting ``"alg": "HS256"`` and signing with the public
key fetched from the provider's JWKS (CWE-345 / CWE-347).
"""
try:
# Use PyJWT's PyJWKClient to fetch JWKS and find signing key.
# The client reads the ``kid`` from the JWT header internally to
# look up the key — that's fine: ``kid`` is not a securityView on GitHub (pinned to 554fb1133a)
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.
Example fix
# verify discovery from inside the container docker exec ragflow-server curl -sS \ 'https://sso.example.com/realms/main/.well-known/openid-configuration' # must print JSON containing issuer, jwks_uri, authorization_endpoint, ...
Defensive patterns
Strategy: retry
Validate before calling
import requests
def discovery_reachable(issuer, timeout=7):
try:
r = requests.get(f"{issuer}/.well-known/openid-configuration", timeout=timeout)
return r.ok and isinstance(r.json(), dict) and "issuer" in r.json()
except Exception:
return False
# run at provider-config save time and fail fast Try / catch
for attempt in range(3):
try:
client = OIDCClient(config)
break
except ValueError as e:
if "Failed to fetch OIDC metadata" in str(e) and "timed out" in str(e).lower() and attempt < 2:
continue # transient timeout - retry
raise ConfigError(f"OIDC discovery failed: {e}") from e Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Missing issuer in configuration.
- WhatsApp session is not running.
- Login failed: invalid JSON response ({exc})
- Failed to connect to SSH host {self.username}@{self.host}:{s
- Unsupported type: {channel_type}
AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15).
Data as JSON: /api/errors/1245d3173487188c.
Report an issue: GitHub.