Repository navigation
Connection Validation
thnak edited this page Jun 6, 2026
·
2 revisions
IMqttConnectionValidator is an optional pre-authentication hook called for every incoming CONNECT packet before the authentication provider runs. Use it to:
- Enforce ClientId format rules
- Block specific IP addresses or subnets
- Pre-populate
SessionItemswith connection-level data (tenant ID, roles, etc.) so subsequent middleware and controllers do not need to repeat the lookup per message
public interface IMqttConnectionValidator
{
ValueTask<MqttConnectionValidationResult> ValidateAsync(
ValidatingConnectionEventArgs context,
CancellationToken cancellationToken = default);
}ValidatingConnectionEventArgs is the raw MQTTnet event args — it exposes ClientId, UserName, Password, RemoteEndPoint, SessionItems, and more.
| Factory | Meaning |
|---|---|
MqttConnectionValidationResult.Accept() |
Allow the connection to proceed |
MqttConnectionValidationResult.AcceptAndBypassAuth() |
Allow the connection and skip the IMqttAuthenticationProvider step |
MqttConnectionValidationResult.Reject(reason, code?) |
Refuse immediately |
reason is logged server-side; code is the MqttConnectReasonCode sent to the client (default: NotAuthorized).
| Property | Type | Description |
|---|---|---|
IsValid |
bool |
true when the connection is accepted |
RejectReason |
string? |
Logged server-side on rejection |
RejectReasonCode |
MqttConnectReasonCode |
Sent to the client on rejection (default: NotAuthorized) |
BypassAuthentication |
bool |
true when the authentication provider step is skipped |
public class ClientIdValidator : IMqttConnectionValidator
{
// Expect ClientIds like "device-{guid}"
private static readonly Regex _pattern =
new(@"^device-[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public ValueTask<MqttConnectionValidationResult> ValidateAsync(
ValidatingConnectionEventArgs ctx, CancellationToken ct)
{
if (!_pattern.IsMatch(ctx.ClientId))
return ValueTask.FromResult(
MqttConnectionValidationResult.Reject(
$"Invalid ClientId format: '{ctx.ClientId}'"));
return ValueTask.FromResult(MqttConnectionValidationResult.Accept());
}
}public class IpAllowlistValidator(IIpAllowlist allowlist) : IMqttConnectionValidator
{
public async ValueTask<MqttConnectionValidationResult> ValidateAsync(
ValidatingConnectionEventArgs ctx, CancellationToken ct)
{
var ip = (ctx.RemoteEndPoint as System.Net.IPEndPoint)?.Address;
if (ip is not null && !await allowlist.IsAllowedAsync(ip, ct))
return MqttConnectionValidationResult.Reject(
$"IP {ip} is not on the allowlist",
MqttConnectReasonCode.Banned);
return MqttConnectionValidationResult.Accept();
}
}Populate SessionItems once here; middleware reads the values on every subsequent message without extra lookups:
public class TenantConnectionValidator(ITenantStore tenants) : IMqttConnectionValidator
{
public async ValueTask<MqttConnectionValidationResult> ValidateAsync(
ValidatingConnectionEventArgs ctx, CancellationToken ct)
{
var tenant = await tenants.FindByUsernameAsync(ctx.UserName, ct);
if (tenant is null)
return MqttConnectionValidationResult.Reject("Tenant not found");
// Stored for the lifetime of the TCP connection
ctx.SessionItems["tenantId"] = tenant.Id;
ctx.SessionItems["tenantPlan"] = tenant.Plan;
return MqttConnectionValidationResult.Accept();
}
}In middleware:
public class TenantMiddleware(ITenantContext ctx) : IMqttMiddleware
{
public Task InvokeAsync(MqttMessageContext context, MqttRequestDelegate next)
{
if (context.SessionItems["tenantId"] is string tid)
ctx.SetTenant(tid);
return next(context);
}
}Use AcceptAndBypassAuth() when the validator has already verified the client (e.g. by ClientId lookup or mutual TLS), making a separate username/password check redundant:
public class DeviceRegistryValidator(IDeviceRegistry registry) : IMqttConnectionValidator
{
public async ValueTask<MqttConnectionValidationResult> ValidateAsync(
ValidatingConnectionEventArgs ctx, CancellationToken ct)
{
if (!await registry.IsRegisteredAsync(ctx.ClientId, ct))
return MqttConnectionValidationResult.Reject("Unknown device");
ctx.SessionItems["deviceId"] = ctx.ClientId;
// Auth provider is skipped — device identity is confirmed by registry lookup
return MqttConnectionValidationResult.AcceptAndBypassAuth();
}
}builder.Services
.AddMqttServer(configuration)
.WithConnectionValidator<TenantConnectionValidator>()
.WithAuthentication<MyAuthProvider>()
.WithControllers<GeneratedMqttControllerRegistration>();- The validator runs before authentication. You can reject the connection before any credentials are checked.
-
AcceptAndBypassAuth()skipsIMqttAuthenticationProviderentirely — use only when the validator provides equivalent identity assurance. - The validator is registered as scoped.
-
SessionItemsis anIDictionary(non-generic) backed by MQTTnet. Cast values to the expected type when reading.