> ## Content Index
> Fetch the complete content index at: https://www.ggorantala.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Kafka SaslAuthenticationException: Authentication Failed — Fix
- URL: https://www.ggorantala.dev/kafka-saslauthenticationexception-authentication-failed/
- Published: 2026-05-26T13:26:00.000Z
- Updated: 2026-08-29T08:25:28.000Z
- Description: Getting SaslAuthenticationException: Authentication failed in Kafka? Fix invalid SASL credentials, JAAS config, and missing SCRAM users with working examples.
- Author: Gopi Gorantala
- Tags: kafka-errors, saslauthenticationexception, kafka-security, sasl-scram, spring-kafka, kafka-docker, Java

Your producer or consumer dies on startup with one of these:

```text
org.apache.kafka.common.errors.SaslAuthenticationException: Authentication failed: Invalid username or password

```

or the SCRAM variant:

```text
org.apache.kafka.common.errors.SaslAuthenticationException: Authentication failed during authentication due to invalid credentials with SASL mechanism SCRAM-SHA-512

```

In Spring Boot logs it usually surfaces wrapped like this:

```text
org.apache.kafka.common.KafkaException: Failed to construct kafka consumer
...
Caused by: org.apache.kafka.common.errors.SaslAuthenticationException: Authentication failed during authentication due to invalid credentials with SASL mechanism SCRAM-SHA-512

```

Two close cousins land people on this page too:

```text
java.lang.IllegalArgumentException: Could not find a 'KafkaClient' entry in the JAAS configuration. System property 'java.security.auth.login.config' is not set

```

```text
org.apache.kafka.common.errors.UnsupportedSaslMechanismException: Client SASL mechanism 'PLAIN' not enabled in the server, enabled mechanisms are [SCRAM-SHA-512]

```

Typical setup where this bites: kafka-clients 3.6–3.8 or Spring Kafka 3.1/3.2 talking to a SASL-enabled broker — Confluent Cloud, Amazon MSK with SASL/SCRAM, Strimzi, or a local Docker KRaft cluster you just secured. Nothing retries, nothing recovers; the client is dead on arrival.

One important thing before you touch any config: **this error is fatal by design.** Since KIP-152 (Kafka 1.0), authentication failures are reported to the client as a non-retriable `SaslAuthenticationException` instead of an endless silent reconnect loop. The client will not fix itself. Something in the credential chain is actually wrong.

## 2\. How to Reproduce It (step-by-step)

Minimal KRaft single-node broker with SASL/PLAIN on the client listener (`apache/kafka:3.7.0`):

```yaml
# docker-compose.yml
services:
  kafka:
    image: apache/kafka:3.7.0
    ports:
      - "9092:9092"
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:29093
      KAFKA_LISTENERS: SASL_EXT://0.0.0.0:9092,INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:29093
      KAFKA_ADVERTISED_LISTENERS: SASL_EXT://localhost:9092,INTERNAL://kafka:29092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: SASL_EXT:SASL_PLAINTEXT,INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_SASL_ENABLED_MECHANISMS: PLAIN
      KAFKA_LISTENER_NAME_SASL__EXT_PLAIN_SASL_JAAS_CONFIG: |
        org.apache.kafka.common.security.plain.PlainLoginModule required
        user_app="app-secret";
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

```

The broker knows exactly one user: `app` / `app-secret`. Now connect with a wrong password:

```bash
cat > client-bad.properties <<'EOF'
security.protocol=SASL_PLAINTEXT
sasl.mechanism=PLAIN
sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \
  username="app" password="wrong-secret";
EOF

kafka-console-producer.sh --bootstrap-server localhost:9092 \
  --topic test --producer.config client-bad.properties

```

Type a message and you get:

```text
ERROR [Producer clientId=console-producer] Connection to node -1 (localhost/127.0.0.1:9092) failed authentication due to: Authentication failed: Invalid username or password
org.apache.kafka.common.errors.SaslAuthenticationException: Authentication failed: Invalid username or password

```

Environment-specific triggers worth knowing:

- **SCRAM user never created.** With SASL/SCRAM the broker validates against stored credentials, not JAAS entries. A fresh cluster has zero SCRAM users — every client fails until you create them (Section 5). This is *the* classic MSK SASL/SCRAM and Strimzi first-run failure.
- **Plaintext client on a SASL port.** The client hangs and eventually times out; the truth is only in the **broker** log: `Failed authentication with /172.18.0.4 (Unexpected Kafka request of type METADATA during SASL handshake.)`
- **Password rotated in the secret store but the app still holds the old one** — worked yesterday, fails after today's deploy.

## 3\. Why It Happens — Surface Level

The SASL handshake completed at the TCP and protocol level, and the broker actively rejected your identity. With PLAIN, the username/password pair didn't match a `user_<name>` entry in the broker's JAAS config for that listener. With SCRAM, the challenge-response proof didn't verify against the salted credential stored in the cluster — either the password is wrong or the user simply doesn't exist for that mechanism.

This is not a network problem, not a listener problem, not TLS. The broker heard you fine and said no.

## 4\. Why It Happens — Under the Hood

The client connection goes through `SaslClientAuthenticator` in a strict state machine: `SaslHandshakeRequest` (advertising the mechanism) → one or more `SaslAuthenticateRequest` round trips carrying the mechanism's opaque tokens → connection promoted to READY.

Three separate failure points map to the three error variants:

**Mechanism negotiation.** The broker only accepts mechanisms listed in `sasl.enabled.mechanisms` *for that listener*. If the client advertises `PLAIN` and the broker only enabled `SCRAM-SHA-512`, the handshake dies immediately with `UnsupportedSaslMechanismException` — you never even get to credentials. Check both sides agree on the exact string; `SCRAM-SHA-256` and `SCRAM-SHA-512` are different mechanisms with independently stored credentials.

**PLAIN validation.** `PlainSaslServer` looks up `user_<username>="<password>"` entries from `listener.name.<listener>.plain.sasl.jaas.config`. It's a static map loaded at broker start (dynamic reconfig via `kafka-configs.sh` is possible since KIP-226). Wrong password or unknown username → `Authentication failed: Invalid username or password`. Note the sharp edge in JAAS: the broker's *own* `username`/`password` fields in that same login module are its inter-broker client credentials, not a user definition — only `user_`\-prefixed keys define accounts.

**SCRAM validation.** SCRAM credentials are salted, iterated hashes (RFC 5802) stored in cluster metadata — the `__cluster_metadata` log in KRaft, ZooKeeper in legacy clusters. The broker never sees your password; it verifies a cryptographic proof against the stored `StoredKey`/`ServerKey`. Consequence: credentials must be **provisioned per mechanism**. Creating a `SCRAM-SHA-256` credential does nothing for a client configured with `SCRAM-SHA-512`. In KRaft, SCRAM management via `kafka-configs.sh` works from Kafka 3.5 (KIP-900); on older KRaft versions this was the source of much pain.

After the failure, the broker deliberately delays the error response (`connection.failed.authentication.delay.ms`, default 100 ms) to blunt brute-force attempts, sends the error, and closes the connection. On the client, `NetworkClient` marks the node with a fatal `AuthenticationException`; `KafkaProducer.send()` fails futures with it, and `KafkaConsumer.poll()` throws it straight out. No retries, no backoff loop — KIP-152 made this a hard stop because retrying an auth failure just hammers the broker.

## 5\. The Fix

### Fix 1: Wrong credentials (PLAIN)

Client side, before/after:

```diff
 security.protocol=SASL_PLAINTEXT
 sasl.mechanism=PLAIN
 sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \
-  username="app" password="wrong-secret";
+  username="app" password="app-secret";

```

If the username itself is unknown, add it broker-side (note the `user_` prefix):

```diff
 KAFKA_LISTENER_NAME_SASL__EXT_PLAIN_SASL_JAAS_CONFIG: |
   org.apache.kafka.common.security.plain.PlainLoginModule required
-  user_app="app-secret";
+  user_app="app-secret"
+  user_analytics="analytics-secret";

```

### Fix 2: Missing SCRAM user

Create the credential for the **exact mechanism** the client uses:

```bash
kafka-configs.sh --bootstrap-server localhost:29092 \
  --alter --entity-type users --entity-name app \
  --add-config 'SCRAM-SHA-512=[iterations=8192,password=app-secret]'

# verify
kafka-configs.sh --bootstrap-server localhost:29092 \
  --describe --entity-type users --entity-name app

```

For a brand-new KRaft cluster where even the admin client can't get in yet, bootstrap the first user at format time:

```bash
kafka-storage.sh format -t "$CLUSTER_ID" -c server.properties \
  --add-scram 'SCRAM-SHA-512=[name=admin,password=admin-secret]'

```

### Fix 3: Mechanism mismatch

```diff
 security.protocol=SASL_SSL
-sasl.mechanism=PLAIN
+sasl.mechanism=SCRAM-SHA-512
-sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \
+sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
   username="app" password="app-secret";

```

The login module class must match the mechanism — `PlainLoginModule` with `sasl.mechanism=SCRAM-SHA-512` fails before the network is even touched.

### Fix 4: `Could not find a 'KafkaClient' entry`

You configured SASL but gave the client no JAAS at all. Don't reach for `-Djava.security.auth.login.config` — set `sasl.jaas.config` per client as shown above. It's scoped, overrides the global JAAS file, and works cleanly with multiple clients per JVM.

### Spring Boot, the right shape

```yaml
spring:
  kafka:
    bootstrap-servers: broker:9092
    security:
      protocol: SASL_SSL
    properties:
      sasl.mechanism: SCRAM-SHA-512
      sasl.jaas.config: >
        org.apache.kafka.common.security.scram.ScramLoginModule required
        username="${KAFKA_USER}"
        password="${KAFKA_PASSWORD}";

```

Watch the trailing semicolon inside `sasl.jaas.config` — omitting it produces a parse error that reads like an auth failure at first glance.

## 6\. Best Practices & The Better Design

`SASL_PLAINTEXT` \+ PLAIN means credentials cross the wire unencrypted and the broker stores them in config files. It's acceptable for a laptop compose file, nowhere else. The production-grade shape:

- **`SASL_SSL` always.** SASL decides *who you are*; TLS protects the exchange. PLAIN without TLS is credential broadcast.
- **SCRAM-SHA-512 over PLAIN** for password-style auth: broker stores salted hashes, passwords never transit in cleartext within the SASL exchange, and users are managed at runtime via `kafka-configs.sh` instead of broker restarts.
- **One principal per service, never a shared "app" user.** Auth failures become attributable, rotation doesn't take down five services, and ACLs can follow least privilege.
- **Credentials from a secret manager** (Vault, AWS Secrets Manager, K8s secrets), injected as env vars — never literals in `application.yml` or compose files that end up in git.
- For fleets at scale, consider **mTLS or OAUTHBEARER (KIP-768, OIDC)** and retire passwords entirely.

Runtime rotation without restarts:

```bash
# rotate the SCRAM password; clients reconnect with the new secret, zero broker downtime
kafka-configs.sh --bootstrap-server broker:9092 \
  --alter --entity-type users --entity-name orders-service \
  --add-config 'SCRAM-SHA-512=[password=new-secret]'

```

Rotate with an overlap window: since SCRAM allows one credential per (user, mechanism), stage rotations by deploying the new secret to clients immediately after altering, or run dual users (`orders-service-a` / `orders-service-b`) blue-green style if you can't coordinate the deploy.

## 7\. How to Prevent It Long-Term

**Monitor failed authentications on the broker.** The metric is `kafka.server:type=socket-server-metrics,...,name=failed-authentication-total` (per listener/processor), with a matching `failed-authentication-rate`. Alert on any sustained non-zero rate: a spike after a deploy means a bad secret shipped; a slow steady rate from an unknown IP is someone guessing passwords. The broker also logs each failure at INFO with the remote address — ship those to your SIEM.

**Fail fast in CI/CD.** A pre-deploy smoke test that builds an `AdminClient` with the candidate credentials and calls `describeCluster()` catches dead secrets before they reach production:

```java
try (AdminClient admin = AdminClient.create(props)) {
    admin.describeCluster().clusterId().get(10, TimeUnit.SECONDS);
} catch (ExecutionException e) {
    if (e.getCause() instanceof SaslAuthenticationException) {
        throw new IllegalStateException("Kafka credentials invalid — aborting deploy", e);
    }
    throw e;
}

```

**Conventions that remove the recurring causes:** mechanism pinned cluster-wide and documented (pick `SCRAM-SHA-512`, delete the rest from `sasl.enabled.mechanisms`); user provisioning as code (Strimzi `KafkaUser`, Terraform, or a Julie/topic-operator pipeline) so "user exists for the right mechanism" is guaranteed, not tribal knowledge; secret rotation runbooks that alter the SCRAM credential and roll the consumers in one change window.

**Spring Kafka note:** by default an auth failure stops the listener container. If you rotate credentials via a secret store and want the container to survive the propagation gap, set `authExceptionRetryInterval` on the container properties — but treat it as a rotation-window bridge, not a license to ignore the alert.

## 8\. Key Takeaways

- `SaslAuthenticationException` is **fatal and non-retriable by design** (KIP-152). The broker heard you and rejected the credentials — fix the config, don't add retries.
- SCRAM credentials are **per user, per mechanism**. A `SCRAM-SHA-256` credential is invisible to a `SCRAM-SHA-512` client. Verify with `kafka-configs.sh --describe --entity-type users`.
- In PLAIN JAAS, only `user_<name>="<password>"` entries define accounts; the module's `username`/`password` are the broker's own inter-broker credentials.
- If the client only shows timeouts, read the **broker** log — "Unexpected Kafka request of type METADATA during SASL handshake" means a plaintext client hit a SASL port.
- Alert on `failed-authentication-total` and smoke-test credentials with an `AdminClient` in CI; bad secrets should fail the deploy, not the 3 a.m. pager.