Skip to main content

Authentication & ACL

Provider chain

auth.providers is tried in order; the first backend that resolves the client decides. If none resolves it and the CONNECT sent no Username, the client falls through to anonymous (when allow_anonymous is on); an anonymous identity has a nil ACL, which means unrestricted.

// The real interface takes a ConnectInfo (clientID, username, password, TLS state).
type Authenticator interface {
Authenticate(ctx context.Context, info ConnectInfo) (*Identity, error)
}
ProviderHow it works
builtinUser/ACL store in SQLite (<storage.dir>/config.db); passwords hashed with PBKDF2-HMAC-SHA256 (bcrypt hashes also verify); managed from the console. A record's auth_type is basic or x509.
x509The client certificate's Common Name selects a builtin record whose auth_type is x509 — no password. Requires the x509 provider, a builtin store, and a CONNECT with no Username.
jwtAn HS256 JWT in the password field; username from username_claim (default sub), ACL from acl_claim (default acl) as an array of {pattern, permission}.
httpA webhook: the broker POSTs {client_id, username, password} (password is base64 in JSON). 401/404 → try the next provider; other non-200 → bad credentials; 200 → the returned {allow, acl}.

ACL model

A rule is { "pattern": "devices/%u/#", "permission": "read" }. Permissions: read, write, readwrite (aliases rw, all), deny. Patterns may use %u (username) and %c (client ID).

Evaluation:

  • Rules are checked in order; the first match wins.
  • No match = deny.
  • A nil rule list = unrestricted; an empty (non-nil) list = deny everything.

Invisibility principle — a topic a client may not see does not exist for it. ACL runs at three points:

  1. Subscribe — reject a filter that intersects no readable rule.
  2. Publish — reject without write (a reason code in 5.0, a silent drop in 3.x).
  3. Delivery — each message for a wildcard subscription, and each retained candidate, is re-checked against the receiver's read permission.