aio-libs/aiohttp · error · ValueError

A ":" is not allowed in username (RFC 1945#section-11.1)

Error message

A ":" is not allowed in username (RFC 1945#section-11.1)

What it means

Raised by DigestAuthMiddleware.__init__ when login contains a ':' character. RFC 1945 section 11.1 forbids ':' in digest usernames because it is the delimiter of the username realm fields in the Authorization header; including it would corrupt the header grammar.

Solutions

  1. Remove the ':' from the username (e.g. use 'domain\\user' or 'user@realm' if the server accepts it).
  2. If you constructed the value by joining fields with ':', switch the separator to '.' or '_'.
  3. Verify against the server's accepted username format (often just the bare sAMAccountName).

Example fix

// before
mw = DigestAuthMiddleware(login='corp:jdoe', password='x')
// after
mw = DigestAuthMiddleware(login='jdoe', password='x')
Defensive patterns

Strategy: validation

Validate before calling

def sanitize_login(login: str) -> str:
    if ':' in login:
        raise ValueError('login must not contain ":" per RFC 1945')
    return login

DigestAuthMiddleware(login=sanitize_login(login), password=password)

Type guard

def is_rfc1945_login(v: str) -> bool:
    return isinstance(v, str) and ':' not in v

Try / catch

try:
    mw = DigestAuthMiddleware(login=login, password=password)
except ValueError as e:
    if ':' in str(e) or 'RFC 1945' in str(e):
        login = login.replace(':', '_')
        mw = DigestAuthMiddleware(login=login, password=password)
    else:
        raise

Prevention

When it happens

Trigger: Constructing DigestAuthMiddleware(login='domain\\user:extra', password='x') or any login value that contains a colon. The ':' in login check fires before any encoding.

Common situations: AD-style 'domain\\user' formatted with a colon by mistake. Email-style 'user:realm' usernames. Concatenating realm and username with ':'.

Related errors


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

Appendix: source

Thrown at aiohttp/client_middleware_digest_auth.py:209

    The core digest calculation is inspired by the implementation in
    https://github.com/requests/requests/blob/v2.18.4/requests/auth.py
    with added support for modern digest auth features and error handling.
    """

    def __init__(
        self,
        login: str,
        password: str,
        preemptive: bool = True,
    ) -> None:
        if login is None:
            raise ValueError("None is not allowed as login value")

        if password is None:
            raise ValueError("None is not allowed as password value")

        if ":" in login:
            raise ValueError('A ":" is not allowed in username (RFC 1945#section-11.1)')

        self._login_str: Final[str] = login
        self._login_bytes: Final[bytes] = login.encode("utf-8")
        self._password_bytes: Final[bytes] = password.encode("utf-8")

        self._last_nonce_bytes = b""
        self._nonce_count = 0
        self._challenge: DigestAuthChallenge = {}
        self._preemptive: bool = preemptive
        # Set of URLs defining the protection space
        self._protection_space: list[str] = []
        # Origin the credentials are scoped to; set on the first request.
        self._origin: URL | None = None

    async def _encode(self, method: str, url: URL, body: Payload | Literal[b""]) -> str:
        """
        Build digest authorization header for the current challenge.

View on GitHub (pinned to d041d4d0fd)