Skip to main content
VulnerabilityPublished

CVE-2026-114617: Authlib · ID token can disable its own nonce validation via the nonce_supported claim

Affects authlibSource GCVE Database

0
Authoritative record:CVE-2026-114617· Medium 4.2

Not known to be exploited · CVSS 4.2 Medium : upgrade to 1.9.0 when convenient.

Data as of 27 Oct 2026, 16:27 UTC

CVSS 3.1 base score
4.2Medium
How severe the flaw is on the 0–10 CVSS scale.
Patch
Available – fixed in 1.9.0
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 22 Oct 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponised: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.

What to do

Upgrade authlib to 1.9.0.

Affected software

ProductAffectedFixed in
PyPIauthlibAffected
  • <1.9.0
Fixed in
  • 1.9.0

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

### Summary `OpenIDMixin.parse_id_token()` reads the `nonce_supported` claim from the ID token it is validating. When that claim is boolean `false`, the expected nonce is set to `None` and nonce validation is skipped for that token. An ID token that carries this claim is therefore accepted in a session whose nonce does not match. A captured, validly signed ID token belonging to a victim can be replayed into an attacker-controlled session, authenticating the attacker as the victim at the relying party. The code is identical in `authlib/integrations/base_client/sync_openid.py:82-83` and `authlib/integrations/base_client/async_openid.py:84-85`, so all framework integrations are affected. Fixed versions: none; reproduced on 1.8.0 (commit c529a61a). Preconditions: the ID token must carry a valid signature and pass `iss` and `aud` validation, and the attacker must be able to supply it to `parse_id_token()`. Authorization code flow is not affected. Implicit and hybrid relying parties, and applications calling `parse_id_token()` directly, are affected. ### Details ## Affected code `authlib/integrations/base_client/sync_openid.py:80-85`, in `parse_id_token()`: ```python claims = claims_cls(token.claims, token.header, claims_options, claims_params) # https://github.com/authlib/authlib/issues/259 if claims.get("nonce_supported") is False: claims.params["nonce"] = None claims.validate(leeway=leeway) ``` `authlib/integrations/base_client/async_openid.py:82-86` contains the same code. ## Source of the claim `claims` is constructed from `token.claims`, which is the decoded payload of the ID token passed to `parse_id_token()`: ```python token = jwt.decode( token["id_token"], key=key_set, registry=JWSRegistry(algorithms=alg_values, strict_check_header=False), ) ... claims = claims_cls(token.claims, token.header, claims_options, claims_params) ``` `claims.get("nonce_supported")` therefore resolves against the ID token payload. It is not read from `load_server_metadata()`, from client configuration, or from any other source under the relying party's control. ## Effect on validation `IDToken.validate_nonce()` at `authlib/oidc/core/claims.py:69-83` performs the comparison only when the expected value is truthy: ```python nonce_value = self.params.get("nonce") if nonce_value: if "nonce" not in self: raise MissingClaimError("nonce") if nonce_value != self["nonce"]: raise InvalidClaimError("nonce") ``` Assigning `claims.params["nonce"] = None` makes `nonce_value` falsy, so both the presence check and the equality check are skipped. No error is raised and `claims.validate()` completes normally. The docstring of that same method states the requirement: > If present in the ID Token, Clients MUST verify that the nonce Claim Value is equal to the value of the nonce parameter sent in the Authentication Request. ## Trigger condition The comparison is `is False`, so only a JSON boolean `false` activates the branch. A string value `"false"` does not, and nonce validation proceeds normally in that case. ## Origin The branch was added in commit `3ca7333c` ("Skip verifying nonce for Apple case") to accommodate a provider that emits a `nonce_supported` claim, as referenced by the inline comment pointing to issue #259. ## Remediation Read the flag from server metadata or client configuration rather than from the token: ```python metadata = self.load_server_metadata() if metadata.get("nonce_supported") is False: claims.params["nonce"] = None ``` This preserves the behavior for relying parties configured against a provider that does not support nonces, while preventing the token from determining whether it is subject to nonce validation. ### Proof of concept ## Environment - authlib 1.8.0, commit `c529a61a` (tag `v1.8.0`) - Python 3.14 - joserfc 1.7.5 - No network access required ``` git clone https://github.com/authlib/authlib.git && cd authlib git checkout c529a61a python3 -m venv .venv .venv/bin/pip install -e . joserfc==1.7.5 .venv/bin/python poc.py ``` ## Configuration The relying party is a subclass of the shipped `OpenIDMixin` supplying only `client_id` and `load_server_metadata()`. `fetch_jwk_set()` returns `metadata["jwks"]` directly when the key set is present in metadata, so no HTTP request is made and no part of the validation path is stubbed. The signing key is a generated RSA key. All three tokens are signed with it, carry `iss` and `aud` matching the relying party configuration, and are within their validity window. The relying party's session nonce is `nonce-the-rp-actually-generated`; every token carries the different nonce `nonce-from-the-captured-token`. ## poc.py ```python from joserfc import jwt from joserfc.jwk import RSAKey from authlib.integrations.base_client.sync_openid import OpenIDMixin ISSUER = "https://as.example" CLIENT_ID = "rp-client" KEY = RSAKey.generate_key(2048, parameters={"kid": "k1"}) JWKS = {"keys": [KEY.as_dict(private=Fal

References

Community reviews

Loading reviews…