Documentation Index
Fetch the complete documentation index at: https://mintlify.com/hivemq/hivemq-community-edition/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Authentication extensions verify client identities when they connect to HiveMQ. The Extension SDK supports:
- Simple Authentication - Username/password authentication for MQTT 3.x and 5.0
- Enhanced Authentication - MQTT 5.0 challenge/response authentication (SASL)
- Multiple Authenticators - Chain multiple authentication mechanisms
- Async Processing - Non-blocking authentication for high performance
Authentication Flow
- Client sends CONNECT packet
- HiveMQ calls
AuthenticatorProvider.getAuthenticator() for each extension (by priority)
- Authenticator validates credentials using
authenticate() method
- Authentication result determines if client connects successfully
See PluginAuthenticatorService.java:40 for internal authentication service.
Implementing an Authenticator
Step 1: Create AuthenticatorProvider
Register your authenticator in extensionStart():
import com.hivemq.extension.sdk.api.ExtensionMain;
import com.hivemq.extension.sdk.api.annotations.NotNull;
import com.hivemq.extension.sdk.api.parameter.*;
import com.hivemq.extension.sdk.api.services.Services;
import com.hivemq.extension.sdk.api.auth.parameter.AuthenticatorProviderInput;
import com.hivemq.extension.sdk.api.auth.SimpleAuthenticator;
import com.hivemq.extension.sdk.api.auth.Authenticator;
public class MyAuthExtension implements ExtensionMain {
@Override
public void extensionStart(
@NotNull ExtensionStartInput input,
@NotNull ExtensionStartOutput output) {
Services services = input.getServices();
// Register authenticator provider
services.authenticationService().setAuthenticatorProvider(
authenticatorProviderInput -> new MySimpleAuthenticator()
);
}
@Override
public void extensionStop(
@NotNull ExtensionStopInput input,
@NotNull ExtensionStopOutput output) {
// Cleanup
}
}
Step 2: Implement SimpleAuthenticator
import com.hivemq.extension.sdk.api.auth.SimpleAuthenticator;
import com.hivemq.extension.sdk.api.auth.parameter.SimpleAuthInput;
import com.hivemq.extension.sdk.api.auth.parameter.SimpleAuthOutput;
import com.hivemq.extension.sdk.api.annotations.NotNull;
import com.hivemq.extension.sdk.api.packets.connect.ConnectPacket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
public class MySimpleAuthenticator implements SimpleAuthenticator {
@Override
public void authenticate(
@NotNull SimpleAuthInput input,
@NotNull SimpleAuthOutput output) {
ConnectPacket connect = input.getConnectPacket();
// Extract credentials
Optional<String> username = connect.getUserName();
Optional<ByteBuffer> password = connect.getPassword();
if (!username.isPresent() || !password.isPresent()) {
output.failAuthentication("Username and password required");
return;
}
String user = username.get();
String pass = StandardCharsets.UTF_8.decode(password.get()).toString();
// Validate credentials
if (isValidCredentials(user, pass)) {
output.authenticateSuccessfully();
} else {
output.failAuthentication("Invalid credentials");
}
}
private boolean isValidCredentials(String username, String password) {
// Check against database, LDAP, etc.
return "admin".equals(username) && "secret".equals(password);
}
}
Authentication Methods
Success
Allow the client to connect:
output.authenticateSuccessfully();
Failure
Reject the client connection:
output.failAuthentication("Invalid credentials");
Optionally specify a reason code (MQTT 5.0):
import com.hivemq.extension.sdk.api.packets.general.DisconnectReasonCode;
output.failAuthentication(
DisconnectReasonCode.NOT_AUTHORIZED,
"Invalid API key"
);
Continue Authentication
Delegate to the next authenticator in the chain:
output.nextExtensionOrDefault();
Use this when:
- Your authenticator doesn’t apply to this client
- You want to support multiple authentication methods
- Falling back to default behavior
Async Authentication
For non-blocking authentication (e.g., database or HTTP lookups):
import com.hivemq.extension.sdk.api.async.Async;
import com.hivemq.extension.sdk.api.async.TimeoutFallback;
import java.time.Duration;
public class AsyncAuthenticator implements SimpleAuthenticator {
private final HttpClient httpClient;
@Override
public void authenticate(
@NotNull SimpleAuthInput input,
@NotNull SimpleAuthOutput output) {
// Enable async mode
Async<SimpleAuthOutput> async = output.async(
Duration.ofSeconds(5),
TimeoutFallback.FAILURE
);
ConnectPacket connect = input.getConnectPacket();
String username = connect.getUserName().orElse("unknown");
// Perform async authentication
httpClient.validateCredentialsAsync(username)
.thenAccept(valid -> {
if (valid) {
async.resume().authenticateSuccessfully();
} else {
async.resume().failAuthentication("Invalid credentials");
}
})
.exceptionally(error -> {
async.resume().failAuthentication("Authentication error: " + error.getMessage());
return null;
});
}
}
Timeout Fallback Options:
TimeoutFallback.FAILURE - Fail authentication on timeout
TimeoutFallback.SUCCESS - Allow connection on timeout (use with caution)
Enhanced Authentication (MQTT 5.0)
For challenge/response authentication using MQTT 5.0 AUTH packets:
import com.hivemq.extension.sdk.api.auth.EnhancedAuthenticator;
import com.hivemq.extension.sdk.api.auth.parameter.*;
import com.hivemq.extension.sdk.api.packets.auth.ModifiableAuthPacket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class SaslAuthenticator implements EnhancedAuthenticator {
@Override
public void authenticate(
@NotNull EnhancedAuthInput input,
@NotNull EnhancedAuthOutput output) {
ModifiableAuthPacket authPacket = output.getAuthPacket();
// Extract authentication method
String method = authPacket.getAuthenticationMethod();
if (!"SCRAM-SHA-256".equals(method)) {
output.failAuthentication("Unsupported auth method");
return;
}
// Extract challenge data
ByteBuffer authData = authPacket.getAuthenticationData().orElse(null);
if (authData == null) {
// First challenge
ByteBuffer challenge = generateChallenge();
output.continueAuthentication(challenge);
} else {
// Validate response
if (validateResponse(authData)) {
output.authenticateSuccessfully();
} else {
output.failAuthentication("Invalid response");
}
}
}
private ByteBuffer generateChallenge() {
String challenge = "server-nonce-12345";
return ByteBuffer.wrap(challenge.getBytes(StandardCharsets.UTF_8));
}
private boolean validateResponse(ByteBuffer response) {
// Validate SASL response
return true;
}
}
Enhanced Authentication Methods
Continue Authentication:
ByteBuffer challengeData = ByteBuffer.wrap("challenge".getBytes());
output.continueAuthentication(challengeData);
Success with Final Data:
ByteBuffer finalData = ByteBuffer.wrap("success".getBytes());
output.authenticateSuccessfully(finalData);
Access client and connection details during authentication:
@Override
public void authenticate(
@NotNull SimpleAuthInput input,
@NotNull SimpleAuthOutput output) {
// Client identifier
String clientId = input.getClientInformation().getClientId();
// Connection information
var connectionInfo = input.getConnectionInformation();
String remoteAddress = connectionInfo.getInetAddress()
.map(addr -> addr.getHostAddress())
.orElse("unknown");
// CONNECT packet details
ConnectPacket connect = input.getConnectPacket();
int keepAlive = connect.getKeepAlive();
boolean cleanStart = connect.getCleanStart();
// Authenticate based on client ID pattern
if (clientId.startsWith("sensor-")) {
output.authenticateSuccessfully();
} else {
output.nextExtensionOrDefault();
}
}
Database Authentication Example
Authenticate against a database:
import java.sql.*;
public class DatabaseAuthenticator implements SimpleAuthenticator {
private final String jdbcUrl;
private final String dbUser;
private final String dbPassword;
public DatabaseAuthenticator(String jdbcUrl, String dbUser, String dbPassword) {
this.jdbcUrl = jdbcUrl;
this.dbUser = dbUser;
this.dbPassword = dbPassword;
}
@Override
public void authenticate(
@NotNull SimpleAuthInput input,
@NotNull SimpleAuthOutput output) {
ConnectPacket connect = input.getConnectPacket();
String username = connect.getUserName().orElse(null);
ByteBuffer passwordBuf = connect.getPassword().orElse(null);
if (username == null || passwordBuf == null) {
output.failAuthentication("Credentials required");
return;
}
String password = StandardCharsets.UTF_8.decode(passwordBuf).toString();
// Async database lookup
Async<SimpleAuthOutput> async = output.async(
Duration.ofSeconds(10),
TimeoutFallback.FAILURE
);
CompletableFuture.supplyAsync(() -> validateInDatabase(username, password))
.thenAccept(valid -> {
if (valid) {
async.resume().authenticateSuccessfully();
} else {
async.resume().failAuthentication("Invalid credentials");
}
});
}
private boolean validateInDatabase(String username, String password) {
String sql = "SELECT password_hash FROM users WHERE username = ?";
try (Connection conn = DriverManager.getConnection(jdbcUrl, dbUser, dbPassword);
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
String storedHash = rs.getString("password_hash");
return verifyPassword(password, storedHash);
}
return false;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
private boolean verifyPassword(String password, String hash) {
// Use BCrypt, PBKDF2, etc.
return BCrypt.checkpw(password, hash);
}
}
Best Practices
Security
- Never Log Passwords - Avoid logging credentials in plain text
- Use Secure Hashing - Store password hashes (BCrypt, PBKDF2, Argon2)
- Rate Limiting - Implement rate limiting to prevent brute force attacks
- Constant Time Comparison - Use constant-time comparison for passwords
- TLS Required - Enforce TLS for credential transmission
- Use Async Mode - Always use async for I/O operations
- Connection Pooling - Pool database connections
- Caching - Cache authentication results when appropriate
- Set Reasonable Timeouts - Don’t block client connections indefinitely
Error Handling
- Fail Securely - On errors, fail authentication rather than allowing access
- Generic Error Messages - Don’t leak information in error messages
- Log Authentication Failures - Monitor failed authentication attempts
- Handle Null Values - Always check for missing credentials
Testing Authentication
Test your authenticator with an MQTT client:
# Using mosquitto_pub
mosquitto_pub -h localhost -p 1883 \
-u admin -P secret \
-t test/topic -m "Hello"
# Using MQTT.js
const mqtt = require('mqtt');
const client = mqtt.connect('mqtt://localhost:1883', {
username: 'admin',
password: 'secret'
});
Troubleshooting
Authentication Always Fails
- Check that
authenticateSuccessfully() is called
- Verify credentials are extracted correctly from CONNECT packet
- Review HiveMQ logs for authentication errors
- Ensure authenticator provider is registered in
extensionStart()
Slow Authentication
- Use async mode for I/O operations
- Check database query performance
- Review timeout settings
- Monitor authentication latency metrics
Multiple Authenticators
When multiple extensions provide authenticators:
- Authenticators execute in extension priority order (lower priority number first)
- Use
nextExtensionOrDefault() to chain authenticators
- First authenticator that returns success/failure wins
- Default behavior applies if all authenticators call
nextExtensionOrDefault()
See Authenticators.java:42 for authenticator registration.
Next Steps
Authorization
Control client publish and subscribe permissions
Client Initializers
Initialize client context after authentication
Extension SDK
Learn more about the Extension SDK API
Packet Interceptors
Intercept MQTT packets