SASL Authentication
SASL (Simple Authentication and Security Layer) is Kafka’s pluggable framework for proving who is connecting. Unlike mTLS, which derives identity from a certificate, SASL negotiates a credential — a password, a Kerberos ticket, or an OAuth token — during the connection handshake and resolves it to a Kafka principal such as User:orders-service. Choosing the right mechanism and pairing it with TLS is the difference between a hardened cluster and one that leaks credentials on the wire. This page compares the four mechanisms and then walks through a complete SASL_SSL + SCRAM setup, end to end.
Comparing the mechanisms
Kafka supports four SASL mechanisms out of the box. They differ in how the credential is stored, whether the secret crosses the network, and what infrastructure they assume.
| Mechanism | Credential | Secret on the wire? | Best for |
|---|---|---|---|
PLAIN | Username + password | Yes (clear text inside the handshake) | Quick setups, only over TLS |
SCRAM-SHA-256 | Salted, hashed password | No (challenge–response) | Password auth, good security |
SCRAM-SHA-512 | Salted, hashed password | No (challenge–response) | Recommended password default |
GSSAPI | Kerberos service ticket | No | Enterprises with existing Kerberos/AD |
OAUTHBEARER | OAuth 2.0 / OIDC bearer token | Token (short-lived) | Cloud-native, federated identity |
PLAIN is the simplest: the client sends a username and password, and the broker validates them against its JAAS config or a custom callback. The password travels in clear text inside the SASL exchange, so it is only acceptable over an encrypted listener.
SCRAM (Salted Challenge Response Authentication Mechanism) is the recommended password-based option. Credentials are stored salted and iterated in the cluster metadata, and the password itself never crosses the network — the client proves knowledge of it via a challenge–response. SCRAM-SHA-512 is the stronger of the two variants and is the sensible default.
GSSAPI integrates with Kerberos, so principals come from your existing KDC or Active Directory. It avoids per-service passwords entirely but requires keytabs, a krb5.conf, and clock synchronisation across the estate.
OAUTHBEARER delegates authentication to an OAuth 2.0 / OIDC provider. Clients present a short-lived bearer token obtained via the client-credentials grant, which suits ephemeral workloads and centralised identity.
SASL/PLAIN sends the password in clear text. Never combine
PLAINwithSASL_PLAINTEXToutside an isolated network — always useSASL_SSL. SCRAM is safer because the secret is never transmitted, so prefer it whenever you control the clients.
Configuring SASL_SSL with SCRAM
SCRAM over TLS is the most common production password setup. There are three moving parts: the broker listener and JAAS config, the SCRAM credentials stored in the cluster, and the client configuration.
Broker configuration
Expose a SASL_SSL listener, enable the SCRAM mechanism, and point the broker at its keystore. In KRaft mode the broker authenticates inter-broker and controller traffic too, so give it its own SCRAM identity.
listeners=BROKER://:9094,EXTERNAL://:9093
listener.security.protocol.map=BROKER:SASL_SSL,EXTERNAL:SASL_SSL
inter.broker.listener.name=BROKER
sasl.enabled.mechanisms=SCRAM-SHA-512
sasl.mechanism.inter.broker.protocol=SCRAM-SHA-512
ssl.keystore.location=/etc/kafka/secrets/broker.keystore.jks
ssl.keystore.password=${KEYSTORE_PASSWORD}
ssl.key.password=${KEY_PASSWORD}
listener.name.broker.scram-sha-512.sasl.jaas.config=\
org.apache.kafka.common.security.scram.ScramLoginModule required \
username="kafka-broker" \
password="${BROKER_SCRAM_PASSWORD}";
The listener-prefixed listener.name.<name>.<mechanism>.sasl.jaas.config form keeps the JAAS configuration inline in server.properties, which is cleaner than the older KAFKA_OPTS=-Djava.security.auth.login.config=... external file.
Creating SCRAM credentials
SCRAM credentials live in the cluster metadata, not in a flat file. Create them with kafka-configs.sh. The broker’s own credential is a chicken-and-egg problem in a brand-new cluster, so add it during bootstrap with kafka-storage.sh format --add-scram, then add application users once the cluster is up.
kafka-configs.sh --bootstrap-server broker:9093 \
--command-config admin.properties \
--alter --add-config \
'SCRAM-SHA-512=[password=orders-secret]' \
--entity-type users --entity-name orders-service
Output:
Completed updating config for user orders-service.
You can confirm the credential exists (the password is never shown, only the salted metadata):
kafka-configs.sh --bootstrap-server broker:9093 \
--command-config admin.properties \
--describe --entity-type users --entity-name orders-service
Output:
SCRAM credential configs for user-principal 'orders-service' are SCRAM-SHA-512=salt=...,iterations=4096
Client configuration
A plain kafka-clients producer or consumer needs the security protocol, the mechanism, the JAAS login module with the username and password, and a truststore that trusts the broker’s CA.
security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-512
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
username="orders-service" \
password="${ORDERS_SECRET}";
ssl.truststore.location=/etc/kafka/secrets/truststore.jks
ssl.truststore.password=${TRUSTSTORE_PASSWORD}
Loaded into a Java client, the same keys apply through ProducerConfig/CommonClientConfigs:
var props = new Properties();
props.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, "broker:9093");
props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
props.put(SaslConfigs.SASL_MECHANISM, "SCRAM-SHA-512");
props.put(SaslConfigs.SASL_JAAS_CONFIG,
"org.apache.kafka.common.security.scram.ScramLoginModule required "
+ "username=\"orders-service\" password=\"" + System.getenv("ORDERS_SECRET") + "\";");
props.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, "/etc/kafka/secrets/truststore.jks");
props.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, System.getenv("TRUSTSTORE_PASSWORD"));
try (var producer = new KafkaProducer<String, String>(
props, new StringSerializer(), new StringSerializer())) {
producer.send(new ProducerRecord<>("orders", "order-42", "{\"id\":42}"));
}
For the same configuration in a Spring Boot application, see Securing Spring Kafka.
Switching mechanisms
The client config is nearly identical across mechanisms — only sasl.mechanism and the JAAS login module change. PLAIN uses PlainLoginModule, OAUTHBEARER uses OAuthBearerLoginModule plus a callback handler, and GSSAPI references a keytab.
# OAUTHBEARER
sasl.mechanism=OAUTHBEARER
sasl.login.callback.handler.class=\
org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginCallbackHandler
sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required \
clientId="orders-service" \
clientSecret="${OAUTH_CLIENT_SECRET}";
sasl.oauthbearer.token.endpoint.url=https://idp.example.com/oauth2/token
Best Practices
- Default to SCRAM-SHA-512 over
SASL_SSL; the password never leaves the client and TLS protects the rest of the exchange. - Never run
PLAINor any mechanism on aSASL_PLAINTEXTlistener exposed beyond a trusted network — pair SASL with TLS. - Give each application its own SCRAM user and a dedicated principal so ACLs and audit logs stay meaningful (see authorization with ACLs).
- Inject JAAS passwords and keystore secrets from environment variables or a secret manager — keep them out of
server.propertiescommitted to source control. - Rotate SCRAM credentials on a schedule by adding the new password before retiring the old one to avoid downtime.
- For cloud-native fleets, prefer OAUTHBEARER with short-lived tokens over long-lived static passwords.