BerriAI/litellm · error · SubjectTokenRejected

IdP rejected the subject token (HTTP {status_code})

Error message

IdP rejected the subject token (HTTP {status_code})

What it means

During RFC 8693 token exchange for an MCP server, the IdP's token endpoint returned a 4xx whose OAuth error code indicts the caller's subject token (e.g. invalid_grant from an expired/revoked refresh token or withdrawn consent) rather than the gateway's own credentials (invalid_client, unauthorized_client, unsupported_grant_type, invalid_target, invalid_scope map to a 500 instead). The proxy wraps it as SubjectTokenRejected and surfaces a 401 to the caller, preserving any IdP step-up claims blob.

Source

Thrown at litellm/proxy/_experimental/mcp_server/outbound_credentials/token_exchange_provider.py:92

    headers: Final = {"Accept": "application/json", **client_auth_headers}
    try:
        client: Final = get_async_httpx_client(llm_provider=httpxSpecialProvider.MCP)  # pyright: ignore
        response: Final = await client.post(url, headers=headers, data=form)  # pyright: ignore
        response.raise_for_status()  # pyright: ignore
        parsed: Final[object] = response.json()  # pyright: ignore
    except httpx.HTTPStatusError as status_err:
        status_code: Final = status_err.response.status_code
        if 400 <= status_code < 500:
            oauth_error, claims = _oauth_error_fields(status_err.response)
            if oauth_error in _GATEWAY_FAULT_OAUTH_ERRORS:
                verbose_logger.warning(
                    "MCP token exchange rejected as %s (HTTP %d); check the gateway client credentials, "
                    "audience, and scope for this server",
                    oauth_error,
                    status_code,
                )
                raise TokenExchangeClientError(oauth_error) from status_err
            raise SubjectTokenRejected(
                f"IdP rejected the subject token (HTTP {status_code})",
                claims=claims,
            ) from status_err
        verbose_logger.warning("MCP token exchange request failed: %s", status_err)
        return None
    except Exception as exc:  # noqa: BLE001
        verbose_logger.warning("MCP token exchange request failed: %s", exc)
        return None
    if not isinstance(parsed, dict):
        # A valid-but-non-object JSON body (list/string/number) would crash the field parsing; map it
        # to a miss so it surfaces as a typed upstream_unavailable, not a 500.
        verbose_logger.warning("MCP token exchange returned non-object JSON (%s)", type(parsed).__name__)
        return None
    return parsed  # pyright: ignore


def build_token_exchanger() -> OboTokenExchanger:
    return OboTokenExchanger(

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Re-authenticate the user against the upstream IdP (re-run the OAuth flow) to obtain a fresh subject token, then retry the tool call.
  2. If the 401 payload carries a claims blob, replay the step-up challenge to the IdP (MSAL-style claims challenge) to satisfy Conditional Access.
  3. Check IdP sign-in/audit logs for the exact rejection reason (revocation, CA policy, audience) if re-auth does not clear it.
Defensive patterns

Strategy: try-catch

Try / catch

resp = await client.post(f"{proxy}/mcp/tool-call", json=payload, headers=headers)
if resp.status_code == 401 and "IdP rejected the subject token" in resp.text:
    claims = extract_claims(resp)  # step-up blob if the IdP sent one
    await reauthenticate_user(claims=claims)   # re-run upstream OAuth (MSAL handles claims challenge)
    resp = await client.post(f"{proxy}/mcp/tool-call", json=payload, headers=headers)
resp.raise_for_status()

Prevention

When it happens

Trigger: Per-user/delegated token exchange where the user's upstream token expired, was revoked, or the IdP demands step-up: POST to the token endpoint answers 400/401 with invalid_grant (or similar non-gateway code) and this exception replaces the credential with a typed rejection.

Common situations: Entra ID Conditional Access/CAE requiring re-authentication; refresh tokens past the rotation window for inactive users; tenant admin revoked the OAuth application's consent; subject token audience changed after an IdP migration.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/6aa6e07edb86495c. Report an issue: GitHub.