{"id":"beb984c6995c79a0","repo":"aio-libs/aiohttp","slug":"a-is-not-allowed-in-login-rfc-7617-section-2","errorCode":null,"errorMessage":"A \":\" is not allowed in login (RFC 7617#section-2)","messagePattern":"A \":\" is not allowed in login \\(RFC 7617#section-2\\)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/helpers.py","lineNumber":167,"sourceCode":"    \"{\",\n    \"}\",\n    \" \",\n    chr(9),\n}\nTOKEN = CHAR ^ CTL ^ SEPARATORS\n\n\njson_re = re.compile(r\"^(?:application/|[\\w.-]+/[\\w.+-]+?\\+)json$\", re.IGNORECASE)\n\n\ndef encode_basic_auth(login: str, password: str = \"\", encoding: str = \"utf-8\") -> str:\n    \"\"\"Encode HTTP Basic Authentication credentials as an Authorization header value.\n\n    Returns a string of the form ``\"Basic <base64>\"`` suitable for use as the\n    value of the ``Authorization`` (or ``Proxy-Authorization``) header.\n    \"\"\"\n    if \":\" in login:\n        raise ValueError('A \":\" is not allowed in login (RFC 7617#section-2)')\n    creds = f\"{login}:{password}\".encode(encoding)\n    return \"Basic \" + base64.b64encode(creds).decode(encoding)\n\n\ndef strip_auth_from_url(url: URL) -> tuple[URL, str | None]:\n    \"\"\"Strip user/password from a URL and return the Authorization header value.\n\n    Returns a tuple of ``(url_without_credentials, authorization_header_value)``.\n    The header value is ``None`` if no credentials were present.\n    \"\"\"\n    # Check raw_user and raw_password first as yarl is likely\n    # to already have these values parsed from the netloc in the cache.\n    if url.raw_user is None and url.raw_password is None:\n        return url, None\n    return url.with_user(None), encode_basic_auth(url.user or \"\", url.password or \"\")\n\n\ndef netrc_from_env() -> netrc.netrc | None:","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/helpers.py#L149-L185","documentation":"Raised by encode_basic_auth when the login contains a colon. RFC 7617 section 2 defines the user-id as everything before the first colon of the 'user:pass' credential string, so an embedded colon in the login makes the encoding ambiguous and aiohttp rejects it with ValueError before base64-encoding.","triggerScenarios":"Calling aiohttp.BasicAuth('user:name', 'pass') or encode_basic_auth('a:b'). Also indirectly via strip_auth_from_url when a URL embeds a user with a colon (URLs forbid it but crafted input can).","commonSituations":"Email-style logins used as HTTP usernames; concatenating domain\\\\user incorrectly; config sourced from upstream that includes colons.","solutions":["Remove the colon from the login or URL-encode the username portion.","Split 'user:pass' strings yourself instead of passing the whole thing as login.","Use BasicAuth(login, password) with separate arguments, never embedding ':' in login."],"exampleFix":"// before\nauth = aiohttp.BasicAuth('john:doe', 'secret')\n// after\nauth = aiohttp.BasicAuth('john', 'secret')","handlingStrategy":"validation","validationCode":"def split_basic_auth(s):\n    login, _, password = s.partition(':')\n    if ':' in login:\n        raise ValueError('colon in login')\n    return login, password","typeGuard":"def is_valid_basic_login(login) -> bool:\n    return isinstance(login, str) and ':' not in login","tryCatchPattern":null,"preventionTips":["Always pass BasicAuth(login, password) with separate args.","Reject or sanitize colons in usernames at the source.","Never paste 'user:pass' as the login field."],"tags":["auth","basic-auth","validation","rfc-7617"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}