infiniflow/ragflow · critical · ValueError

Error parsing ID Token: {e}

Error message

Error parsing ID Token: {e}

What it means

OIDCClient.parse_id_token verifies the JWT signature via JWKS and python-jwt.decode with an allowlist of algorithms pinned from discovery metadata, plus audience (client_id) and issuer checks. Any failure - key lookup, signature, exp/iat, aud, iss, or alg allowlist - is re-raised as ValueError('Error parsing ID Token: {e}') (api/apps/auth/oidc.py:143).

Source

Thrown at api/apps/auth/oidc.py:143

        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 security
            # decision, the signature still proves which key was used.
            jwks_cli = jwt.PyJWKClient(self.jwks_uri)
            signing_key = jwks_cli.get_signing_key_from_jwt(id_token).key

            # Decode and verify signature against the pinned allowlist.
            decoded_token = jwt.decode(
                id_token,
                key=signing_key,
                algorithms=list(self.id_token_signing_algs),
                audience=str(self.client_id),
                issuer=self.issuer,
            )
            return decoded_token
        except Exception as e:
            raise ValueError(f"Error parsing ID Token: {e}")

    def fetch_user_info(self, access_token, id_token=None, **kwargs):
        """
        Fetch user info.
        """
        user_info = {}
        if id_token:
            user_info = self.parse_id_token(id_token)
        user_info.update(super().fetch_user_info(access_token).to_dict())
        return self.normalize_user_info(user_info)

    async def async_fetch_user_info(self, access_token, id_token=None, **kwargs):
        user_info = {}
        if id_token:
            user_info = self.parse_id_token(id_token)
        user_info.update((await super().async_fetch_user_info(access_token)).to_dict())
        return self.normalize_user_info(user_info)

View on GitHub (pinned to 554fb1133a)

Solutions

  1. Ensure RAGFlow's client_id exactly equals the IdP client ID (the aud in the token).
  2. Make the configured issuer byte-identical to the iss claim - watch trailing slashes and realm paths.
  3. Paste the ID token into a JWT debugger and check exp/iss/aud against your config and current time; fix clock skew (NTP) if expiry fires early.
  4. If keys rotated, verify the jwks_uri from discovery is reachable and serves the current keys; confirm the IdP's signing alg (e.g. RS256) is in the discovery metadata's supported list.

Example fix

# inspect the token's claims against config
# decode header.payload (no verification) and compare:
#   iss  == https://sso.example.com/realms/main  (exact, incl. no trailing slash)
#   aud  == <your client_id>
#   exp  > now
Defensive patterns

Strategy: try-catch

Validate before calling

# no caller-side code can make a provider-issued JWT valid; precheck config invariants
assert cfg["client_id"] == expected_aud
assert cfg["issuer"] == expected_iss
assert abs(time.time() - ntp_now()) < 60  # clock skew guard on the host

Try / catch

try:
    claims = client.parse_id_token(id_token)
except ValueError as e:
    msg = str(e)
    if "exp" in msg.lower() or "expired" in msg.lower():
        restart_authorization()  # stale token - do not retry the same one
    elif "aud" in msg.lower():
        raise ConfigError("client_id does not match token audience")
    elif "iss" in msg.lower():
        raise ConfigError("issuer config does not match token issuer")
    else:
        raise

Prevention

When it happens

Trigger: client_id in RAGFlow differs from the token's aud claim; issuer config differs from the token's iss claim (trailing slash!); expired token (exp passed, e.g. replayed callback or clock skew); JWKS endpoint unreachable or token signed with a key absent from JWKS (IdP key rotation); token alg not in the allowlist intersected with _ALLOWED_OIDC_SIGNING_ALGS; malformed token string.

Common situations: Client ID changed in RAGFlow but not at the IdP (or vice versa); Keycloak/other IdP realm URL with inconsistent trailing slash between discovery issuer and configured issuer; IdP signing-key rotation with cached JWKS; server clock drift causing premature expiry.

Related errors


AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15). Data as JSON: /api/errors/f4cc1202f3ea72ab. Report an issue: GitHub.