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)
}
| Provider | How it works |
|---|---|
builtin | User/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. |
x509 | The 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. |
jwt | An HS256 JWT in the password field; username from username_claim (default sub), ACL from acl_claim (default acl) as an array of {pattern, permission}. |
http | A 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:
- Subscribe — reject a filter that intersects no readable rule.
- Publish — reject without write (a reason code in 5.0, a silent drop in 3.x).
- Delivery — each message for a wildcard subscription, and each retained candidate, is re-checked against the receiver's read permission.