aio-libs/aiohttp · error · ValueError

Cannot combine AUTHORIZATION header with credentials…

Error message

Cannot combine AUTHORIZATION header with credentials encoded in URL

What it means

Raised inside the request loop on the initial request (history is empty) when the URL contains userinfo (user:pass@host) AND an explicit Authorization header is present. aiohttp refuses to silently let URL credentials shadow a header the caller set, since that is a classic source of auth-bypass and credential-leak bugs.

Solutions

  1. Remove credentials from the URL and keep only the Authorization header.
  2. Or remove the Authorization header and let the URL's userinfo drive Basic auth.
  3. If using a session-level Authorization header, strip userinfo from request URLs at build time.

Example fix

// before
await session.get('https://user:pass@host/path', headers={'Authorization': 'Bearer x'})
// after
await session.get('https://host/path', headers={'Authorization': 'Bearer x'})
Defensive patterns

Strategy: validation

Validate before calling

from aiohttp import URL
from multidict import CIMultiDict

def strip_url_userinfo_if_auth_header(url, headers):
    if any(k.lower() == 'authorization' for k in headers):
        u = URL(url)
        if u.user is not None:
            url = u.with_user(None).with_password(None)
    return url

Type guard

from aiohttp import URL

def url_has_userinfo(u) -> bool:
    u = URL(u)
    return u.user is not None

Try / catch

try:
    resp = await session.get(url, headers=headers)
except ValueError as e:
    if 'AUTHORIZATION' in str(e):
        from aiohttp import URL
        u = URL(url)
        resp = await session.get(str(u.with_user(None).with_password(None)), headers=headers)
    else:
        raise

Prevention

When it happens

Trigger: Calling session.get('https://user:pass@host/path', headers={'Authorization': 'Bearer x'}). Also when default headers on the session include Authorization and the URL has embedded credentials.

Common situations: Copy-pasting a URL with credentials from a deploy dashboard while also adding a token header. Session-level default Authorization header combined with a per-request URL that carries basic auth. Migrating from a lib that silently preferred one source.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/d6f1c55ee555b43d. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/client.py:629

                while True:
                    url, auth_from_url = strip_auth_from_url(url)
                    if not url.raw_host:
                        # NOTE: Bail early, otherwise, causes `InvalidURL` through
                        # NOTE: `self._request_class()` below.
                        err_exc_cls = (
                            InvalidUrlRedirectClientError
                            if redirects
                            else InvalidUrlClientError
                        )
                        raise err_exc_cls(url)

                    if auth_from_url is not None:
                        # URL-embedded credentials override any Authorization
                        # header already present (e.g. carried from a previous
                        # redirect). On the initial request, refuse to silently
                        # shadow an explicit Authorization header.
                        if not history and hdrs.AUTHORIZATION in headers:
                            raise ValueError(
                                "Cannot combine AUTHORIZATION header with "
                                "credentials encoded in URL"
                            )
                        headers[hdrs.AUTHORIZATION] = auth_from_url
                    elif (
                        self._trust_env
                        and url.host is not None
                        and hdrs.AUTHORIZATION not in headers
                    ):
                        # Fall back to ~/.netrc credentials when trust_env is set.
                        netrc_auth = await self._loop.run_in_executor(
                            None, self._get_netrc_auth, url.host
                        )
                        if netrc_auth is not None:
                            headers[hdrs.AUTHORIZATION] = netrc_auth

                    all_cookies = self._cookie_jar.filter_cookies(url)

View on GitHub (pinned to d041d4d0fd)