redis/redis-py · critical · AuthenticationError

Invalid Username or Password

Error message

Invalid Username or Password

What it means

Raised as AuthenticationError during the async connection handshake (on_connect) after the AUTH command's response is not the literal string 'OK'. The library sends AUTH with the configured username/password (retrying with just the password if the server rejects the arg count, indicating Redis < 6.0) and treats any non-OK reply as failed credentials. This is a hard failure: the connection is never returned to the pool healthy.

Solutions

  1. Verify the AUTH credentials directly with redis-cli -u '<url>' AUTH against the same endpoint.
  2. Confirm the password does not contain URL-special characters that need percent-encoding when passed via redis://user:pass@host; either percent-encode or pass username=/password= kwargs instead.
  3. Check the server's ACL LIST / CONFIG GET requirepass to confirm the user exists and the password matches.
  4. If the server is Redis < 6.0, pass only password= (no username) so the legacy single-arg AUTH path is taken.
  5. Rotate/refresh the credential source if using a token provider (e.g. EntraID) and re-create the client.

Example fix

// before
r = redis.asyncio.from_url('redis://default:pa$$w0rd@host:6379')
// after
r = redis.asyncio.Redis(host='host', username='default', password='pa$$w0rd')
Defensive patterns

Strategy: try-catch

Validate before calling

import redis.asyncio as aioredis

async def probe_auth(url: str) -> bool:
    r = aioredis.from_url(url, socket_connect_timeout=2)
    try:
        await r.ping()
        return True
    except aioredis.AuthenticationError:
        return False
    finally:
        await r.aclose()

Type guard

from redis.exceptions import AuthenticationError

def is_auth_error(exc: BaseException) -> bool:
    return isinstance(exc, AuthenticationError)

Try / catch

import redis.asyncio as aioredis
from redis.exceptions import AuthenticationError

try:
    await client.ping()
except AuthenticationError:
    # credentials wrong: do NOT retry blindly; refresh creds then recreate client
    client = await rebuild_client_with_fresh_credentials()

Prevention

When it happens

Trigger: Constructing redis.asyncio.Redis(... username=..., password=...) or from_url('redis://user:wrongpass@host') and issuing any command, which forces on_connect() -> AUTH. Also fires when a Redis 6+ ACL user lacks permission or the password is stale/wrong.

Common situations: Wrong password in env var/secret; rotated credentials not yet picked up; connecting with a username to a Redis < 6.0 server that uses legacy requirepass (the single-arg retry path); ACL user configured without +@all or with a denied password; copy-paste error including the URL-encoded percent in the literal password.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/975cf63ef94737eb. Report an issue: GitHub.

Appendix: source

Thrown at redis/asyncio/connection.py:993

            ) != int(self.protocol):
                raise ConnectionError("Invalid RESP version")
        # avoid checking health here -- PING will fail if we try
        # to check the health prior to the AUTH
        elif auth_args:
            await self.send_command("AUTH", *auth_args, check_health=False)

            try:
                auth_response = await self.read_response()
            except AuthenticationWrongNumberOfArgsError:
                # a username and password were specified but the Redis
                # server seems to be < 6.0.0 which expects a single password
                # arg. retry auth with just the password.
                # https://github.com/andymccurdy/redis-py/issues/1274
                await self.send_command("AUTH", auth_args[-1], check_health=False)
                auth_response = await self.read_response()

            if str_if_bytes(auth_response) != "OK":
                raise AuthenticationError("Invalid Username or Password")

        # if resp version is specified, switch to it
        elif check_protocol_version(self.protocol, 3):
            if isinstance(self._parser, _AsyncRESP2Parser):
                self.set_parser(_AsyncRESP3Parser)
                # update cluster exception classes
                self._parser.EXCEPTION_CLASSES = parser.EXCEPTION_CLASSES
                self._parser.on_connect(self)
            await self.send_command("HELLO", self.protocol, check_health=check_health)
            response = await self.read_response()
            # if response.get(b"proto") != self.protocol and response.get(
            #     "proto"
            # ) != self.protocol:
            #     raise ConnectionError("Invalid RESP version")

        # Activate maintenance notifications for this connection
        # if enabled in the configuration
        # This is a no-op if maintenance notifications are not enabled

View on GitHub (pinned to 6a6b581b48)