fix: OIDC login fails when the provider adds a private JOSE header (#28065)

Logging in through CyberArk Identity dies at the callback with "Unsupported {'app_id'} in header" and the user sees "The email or password provided is incorrect". Any provider that puts a vendor-specific parameter in the ID token header hits this; CAS was already patched by name, CyberArk is the next one.

Authlib 1.7 verifies ID tokens with joserfc, which rejects header parameters it does not recognise. The old fix registered `client_id` so CAS would work, which only ever fixes one provider at a time. This turns off the unknown-header rejection instead, so any private header parameter is ignored rather than fatal. Signature verification, the algorithm allowlist, `crit` handling and value validation of registered headers all still run, so nothing that actually protects the token is relaxed.

Fixes #28062
This commit is contained in:
Classic298
2026-08-10 20:06:31 -06:00
committed by GitHub
parent a41faa3c22
commit 5462c02af0
+5 -9
View File
@@ -10,6 +10,7 @@ import urllib
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from functools import partialmethod
from types import SimpleNamespace
from typing import Literal, Optional
@@ -25,7 +26,6 @@ from fastapi import (
)
from joserfc.errors import BadSignatureError
from joserfc.jws import JWSRegistry
from joserfc.registry import HeaderParameter
from mcp.shared.auth import (
OAuthClientMetadata as MCPOAuthClientMetadata,
)
@@ -90,6 +90,10 @@ from open_webui.utils.misc import parse_duration
from open_webui.utils.validate import validate_profile_image_url
from starlette.responses import RedirectResponse
# Some IdPs put private params in ID token JOSE headers (CAS: client_id, CyberArk: app_id).
# Authlib exposes no way to pass a registry, so relax it globally; crit, alg and signature checks still apply.
JWSRegistry.__init__ = partialmethod(JWSRegistry.__init__, strict_check_header=False)
class OAuthClientMetadata(MCPOAuthClientMetadata):
token_endpoint_auth_method: Literal['none', 'client_secret_basic', 'client_secret_post'] = 'client_secret_post'
@@ -193,14 +197,6 @@ DEFAULT_TOKEN_EXPIRY_SECONDS = 3600
NON_EXPIRING_TOKEN_EXPIRES_AT = 253402300799 # 9999-12-31 23:59:59 UTC
# Apereo CAS includes client_id in ID token JWS headers; Authlib 1.7/joserfc
# rejects unknown headers unless we register the provider extension.
JWSRegistry.default_header_registry.setdefault(
'client_id',
HeaderParameter('OAuth client identifier', 'str'),
)
def _normalize_token_expiry(token: dict) -> dict:
"""Ensure a token dict always has a numeric ``expires_at``.