Built-in database example
Built-in database example
The built-in database holds MQTT users in the broker. It needs no external server. Use it for fleets that the broker manages alone. It stores users, checks passwords, and delivers messages.
sequenceDiagram participant Ops as Operator participant Api as Management API participant Kernel as Kernel Ops->>Api: POST /api/v1/auth/users sensor-01 Api-->>Ops: 201 Ops->>Kernel: CONNECT sensor-01 + password Kernel-->>Ops: CONNACK ok or deniedBefore you start
- Read Authentication.
- Keep ports 1883 and 18083 free.
- Pick a password with a letter and a digit.
Worked example
Start with a closed broker.
[node]id = "docs-builtin"
[listeners.tcp]bind = "0.0.0.0:1883"
[listeners.ws]bind = "0.0.0.0:8083"path = "/mqtt"
[listeners.api]bind = "0.0.0.0:18083"
[logging]level = "warn"
[auth]enabled = trueallow_anonymous = falseValidate the directory before you start. The command prints ok.
"${INDRAMQTT_BIN:-indramqtt}" --config-dir "${DOCS_CFG_DIR:-./docs-start}" --check-configokCheck that it works
Create the user sensor-01. The call answers 201. It also grants publish and subscribe access to sensors/01.
python3 - <<'PY'import json, os, urllib.requestport = os.environ.get("API_PORT", "18083")key = os.environ["API_KEY"]
def call(method, path, body=None): req = urllib.request.Request( f"http://127.0.0.1:{port}{path}", data=None if body is None else json.dumps(body).encode(), method=method, headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"}, ) with urllib.request.urlopen(req, timeout=5) as res: return res.status
status = call("POST", "/api/v1/auth/users", {"username": "sensor-01", "password": "Sensor-docs-pw-1"})assert status == 201for action in ("publish", "subscribe"): assert call("POST", "/api/v1/auth/acls", {"client_pattern": "*", "action": action, "topic_pattern": "sensors/01", "allow": True}) == 201print(status)PY201Connect as sensor-01. The right password opens access. A wrong password fails. The check prints ok and then denied.
python3 - <<'PY'import os, socket, structmqtt_port = int(os.environ.get("MQTT_PORT", "1883"))
def packet(kind, body): return bytes([kind, len(body)]) + body
def field(data): return struct.pack(">H", len(data)) + data
def try_connect(client_id, username, password): sock = socket.create_connection(("127.0.0.1", mqtt_port), timeout=5) sock.settimeout(5) body = (b"\x00\x04MQTT\x04\xc2\x00\x3c" + field(client_id) + field(username) + field(password)) sock.sendall(packet(0x10, body)) try: reply = sock.recv(4) except OSError: return None return sock if reply == b"\x20\x02\x00\x00" else None
topic = b"sensors/01"sub = try_connect(b"docs-sensor-sub", b"sensor-01", b"Sensor-docs-pw-1")assert sub is not Nonesub.sendall(packet(0x82, b"\x00\x01" + field(topic) + b"\x00"))assert sub.recv(5)[:1] == b"\x90"pub = try_connect(b"docs-sensor-pub", b"sensor-01", b"Sensor-docs-pw-1")assert pub is not Nonepub.sendall(packet(0x30, field(topic) + b"hello-sensor"))sub.settimeout(5)assert sub.recv(100).endswith(b"hello-sensor")print("ok")print("denied" if try_connect(b"docs-sensor-bad", b"sensor-01", b"wrong-password") is None else "open")PYokdeniedWith zero users the broker is open. Once a user exists, anonymous CONNECT fails while users exist, unless the broker runs with --allow-anonymous.
Limits
- Stored verifiers cover legacy SHA-256 hex, bcrypt, PBKDF2 and Argon2id. New passwords hash under the configured policy.
- Bcrypt passwords hold at most 72 bytes. The bcrypt primitive truncates input at 72 bytes, so longer values fail closed rather than verify by prefix.
- The broker holds at most 100000 users. One import batch holds at most 10000 entries.
- Concurrent expensive checks share one verification per credential. 32 concurrent checks cap memory and CPU from memory-hard verification. Past 32 concurrent checks new CONNECTs fail closed.
- Registry writes persist across restarts. A failed save answers HTTP 500 and never stays silent.
Next steps
- Grant topics with Authorization.
- Use tokens with the JWT example.
- Manage operators with Management users.