GitHub Advisoryadvisory · PyPI
GHSA-q686-2qmg-hqc7 Authlib: OpenID implicit grant checks raw scope instead of allowed scope before issuing ID tokens
MEDIUM
Official page ↗
- Aliases
- CVE-2026-114619
- Ecosystem
- PyPI
- Packages
- authlib
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:N
- Weaknesses
- CWE-863
- Reviewed
- 2026-10-27
Affected packages
• PyPI authlib: < 1.9.0 → fixed in 1.9.0
Description
Report metadata
| Field | Value |
| --- | --- |
| Package | `authlib` (PyPI, `pkg:pypi/authlib`) |
| Repository | https://github.com/authlib/authlib |
| Component | `authlib.oidc.core.grants.implicit.OpenIDImplicitGrant.validate_authorization_request` |
| Vulnerability class | Authorization bypass: a client's scope restriction is bypassed for OpenID Connect ID token issuance |
| Verified against | `c529a61af40e3efe05286140e00fbdced07d7987` (v1.8.0). Earlier releases were not checked. |
| API surface | Public API (`OpenIDImplicitGrant`, registered with `server.register_grant(...)`) |
| Affected in default configuration | Yes. No non-default option is needed once `OpenIDImplicitGrant` is registered. Client metadata decides exposure: Authlib's standard `ClientMixin` checks response type and scope independently, so a client can be permitted `id_token` responses without being allowed the `openid` scope. |
Summary
`OpenIDImplicitGrant.validate_authorization_request()` first runs the parent `ImplicitGrant` validation, which filters the requested scope through `client.get_allowed_scope()` and stores the result in `request.scope`. It then decides whether this is an OpenID request by checking the raw, client-supplied `request.payload.scope` instead of that filtered value. A client that is allowed `profile` but not `openid` can therefore request `scope=openid profile`, pass the check, and receive a signed ID token (with `sub` and profile claims) once the user consents. This happens even when Authlib's built-in, scope-filtering `BearerTokenGenerator` is in use.
The tainted input is the authorization request's `scope` parameter. The OAuth client fully controls it, and the client is the very party the scope restriction is meant to constrain. Any registered client can add `openid` to its request if it is permitted an `id_token` or `id_token token` response type and its allowed scopes exclude `openid` but include a claim-bearing scope such as `profile`.
Sibling paths treat the same client correctly, which suggests the raw-scope check is a bug rather than intended behaviour:
• `ImplicitGrant.validate_authorization_request()` -> filters the requested scope through `client.get_allowed_scope()` into `request.scope` (safe, `authlib/oauth2/rfc6749/grants/implicit.py` lines 141-147)
• `OpenIDImplicitGrant.validate_authorization_request()` -> checks the raw `request.payload.scope` (unsafe, `authlib/oidc/core/grants/implicit.py` line 72)
• `UserInfoEndpoint` -> rejects the same client's access token with 403 `insufficient_scope` (safe; observed during verification, line not recorded)
• OIDC authorization code flow -> issues no `id_token` for the same client (safe; observed during verification, line not recorded)
Impact
A registered OAuth client that the operator has excluded from the `openid` scope can get an OpenID Connect ID token for any user who logs in and approves its authorization request. During verification the provider returned an RS256-signed ID token carrying `iss`, `aud` (the requesting client), `nonce`, `auth_time`, `at_hash`, `sub` and `name`, while the granted scope was only `profile`. A consent page built the way the Authlib docs show lists only `profile`, so the user is not shown that an identity token will be issued.
Without this bug, the same request fails with `InvalidScopeError` ("Missing 'openid' scope") because the client's resolved scope does not include `openid`. The new capability is the ID token itself: a signed assertion of the user's identity (`sub`) plus claims from the client's other allowed scopes. Gating that token is exactly what the `openid` scope is for. The access token stays restricted when the built-in token generator is used: Authlib's `UserInfoEndpoint` rejects it with 403 `insufficient_scope`.
Scoring note: we did not compute a CVSS score. An initial triage rated this High. After dynamic verification we assess practical severity as closer to Medium, because:
1. The attacker must be a registered client that is permitted `id_token` response types.
2. The user must log in and consent.
3. The ID token's `aud` is the attacking client.
Two kinds of deployment are not affected:
• Deployments that use only the OIDC authorization code flow. During verification, that flow issued no ID token for the same client.
• Deployments where every client permitted an `id_token` or `id_token token` response type is also allowed the `openid` scope.
Preconditions
1. The authorization server registers `OpenIDImplicitGrant` (or a subclass), e.g. `server.register_grant(MyOpenIDImplicitGrant)`. The vulnerable method runs on every OIDC implicit authorization request.
2. A client's metadata permits an OIDC implicit response type (`id_token` or `id_token token`). Nothing in Authlib ties this to the `openid` scope. The standard SQLAlchemy `ClientMixin` checks response type and scope independently, and it filters requested scopes against the client's allowed scopes at `authlib/integrations/sqla_oauth2/client_mixin.py:124-129`.
3. That client's allowed scopes exclude `openid` but include another claim-bearing scope, such as `profile`.
4. The attacker controls that registered client and sends an authorization request that adds `openid` to otherwise allowed scopes (e.g. `scope=openid profile`), with a `nonce`.
5. A user logs in and approves the request.
6. Branch condition: the raw `request.payload.scope` contains `openid` while the filtered `request.scope` does not. So `is_openid_scope(self.request.payload.scope)` is true at `implicit.py:72` and no `InvalidScopeError` is raised.
No non-standard usage, monkey-patching or unusual configuration is needed: this is the public grant used as intended. Keeping a client's response types and allowed scopes in sync is not documented as the integrator's responsibility. The parent grant stores the filtered `request.scope` specifically so that authorization decisions use it. We did not capture a link to the relevant Authlib docs page, but the verification built its consent page the way those docs show.
Data flow
1. Source: the OAuth client controls the authorization request's `scope` parameter, e.g. `GET /authorize?response_type=id_token%20token&client_id=c&redirect_uri=https%3A%2F%2Fc%2Fcb&scope=openid%20profile&nonce=n`. The raw value remains available as `request.payload.scope`.
2. Propagation: `ImplicitGrant.validate_authorization_request()` filters the requested scope through `client.get_allowed_scope()` and stores the result in `request.scope` (here `'profile'`). `request.payload.scope` still holds the raw `openid profile` (`authlib/oauth2/rfc6749/grants/implicit.py:141-147`).
3. Sink: `OpenIDImplicitGrant.validate_authorization_request()` uses the raw value to decide whether this is an OpenID request: `if not is_openid_scope(self.request.payload.scope):` (`authlib/oidc/core/grants/implicit.py:72`). The injected `openid` satisfies the check, so the method returns the redirect URI instead of raising `InvalidScopeError` (`authlib/oidc/core/grants/implicit.py:70-84`).
4. Reachability: `OpenIDImplicitGrant` is the public grant that serves OIDC implicit authorization requests. After the user consents, `creat…
Related
Alias · CVE (1)
- CVE-2026-114619 Authlib is a Python library which builds OAuth and OpenID Connect servers. vulnerability
Weakness · CWE (1)
- CWE-863 Incorrect Authorization weakness
References
- https://github.com/authlib/authlib/security/advisories/GHSA-q686-2qmg-hqc7 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-114619 advisory
- https://github.com/authlib/authlib/pull/945 web
- https://github.com/authlib/authlib/commit/e3bd41cb29dcfee434453b135ffb487884baeedf web
- https://github.com/authlib/authlib package
- https://github.com/authlib/authlib/releases/tag/v1.9.0 web