This is a development version of the documentation. Content may change without notice.
Voke Documentation
PLC / Devices

Password onboarding

Provision USERNAME + PASSWORD credentials for your PLC via the Voke REST API or admin SPA.

The PASSWORD listener is the simplest path to a working MQTT connection. Credentials are managed entirely by Voke — no certificates or JWT keys are required on the PLC.

Listener port: 8885 (same in production and local dev)


When to use

  • Local dev and CI smoke tests.
  • Industrial controllers where firmware cannot implement TLS client certificates or RS256 JWT signing.
  • Low-trust networks where the management overhead of mTLS is not justified.

Do not use the PASSWORD listener for high-assurance production deployments. Passwords are long-lived until explicitly rotated and do not provide a cryptographic device identity. Use mTLS or JWT instead.


How it works

  1. You call POST /api/v1/plants/{plantId}/provision with the plant in PENDING status and authMethod = PASSWORD.
  2. Voke's MqttPasswordService generates a short username (plc-<first 8 hex chars of plantId>) and a random 32-character alphanumeric password.
  3. The password is hashed with PBKDF2-SHA512 and written into /mosquitto/config/passwd-devices.
  4. An ACL block scoped to cpi/{plantId}/# is appended to /mosquitto/config/acl-devices.
  5. Mosquitto receives SIGHUP via docker kill --signal=SIGHUP cpi-mosquitto and reloads both files without dropping existing connections.
  6. The provisioning package (including the plaintext password — shown only once) is returned in the response body.

Provisioning credentials

Via REST API

The plant must exist and be in PENDING lifecycle status. The authMethod is set when creating the plant record.

POST /api/v1/plants/{plantId}/provision
Authorization: Cookie / Bearer token with ORG_ADMIN role
X-Org-Id: {organizationId}

No request body is needed — the provisioning type is determined by the plant's stored authMethod field.

Response (PASSWORD variant):

{
  "plantId": "a1b2c3d4-0000-0000-0000-000000000001",
  "authMethod": "PASSWORD",
  "connection": {
    "broker": "mqtts://mqtt.voke.lovinka.com",
    "port": 8885,
    "useTls": true,
    "clientId": "plc-a1b2c3d4"
  },
  "auth": {
    "username": "plc-a1b2c3d4",
    "password": "Xy7Kq..."
  },
  "tls": {
    "caCertPem": "-----BEGIN CERTIFICATE-----\n..."
  },
  "topics": {
    "telemetry": "cpi/a1b2c3d4-0000-0000-0000-000000000001/telemetry",
    "command":   "cpi/a1b2c3d4-0000-0000-0000-000000000001/command",
    "ack":       "cpi/a1b2c3d4-0000-0000-0000-000000000001/ack",
    "status":    "cpi/a1b2c3d4-0000-0000-0000-000000000001/status",
    "alarm":     "cpi/a1b2c3d4-0000-0000-0000-000000000001/alarm"
  },
  "recommended": {
    "qos": 1,
    "keepAlive": 60
  }
}

The plaintext password is returned once in this response. Store it securely; the API only stores the bcrypt hash and cannot recover the plaintext.

Also available as a downloadable JSON file:

GET /api/v1/plants/{plantId}/provision/download

Returns the same body with a Content-Disposition: attachment header.

Via Admin SPA

  1. Open Voke admin → Plants → select your plant.
  2. Go to the Security tab.
  3. Click Provision (requires the plant to be in PENDING status with authMethod = PASSWORD).
  4. Copy the displayed username and password — the dialog closes after one view.

Username format

Mosquitto passwords are limited to 65,535 bytes but Voke shortens the username to avoid embedded null issues with some MQTT clients:

plc-<first 8 hex chars of plantId (UUID, dashes removed)>

For plant a1b2c3d4-0000-0000-0000-000000000001 the username is plc-a1b2c3d4.

Use this same string as both the MQTT client_id and username.


Connecting

import paho.mqtt.client as mqtt

HOST      = "mqtt.voke.lovinka.com"  # or "localhost" for local dev
PORT      = 8885                     # same port in prod and dev
PLANT_ID  = "a1b2c3d4-0000-0000-0000-000000000001"
USERNAME  = "plc-a1b2c3d4"          # from provisioning package
PASSWORD  = "Xy7Kq..."               # from provisioning package
CA_CERT   = None  # set to "/path/to/ca.crt" for local dev without system trust

client = mqtt.Client(client_id=USERNAME, protocol=mqtt.MQTTv5)
client.username_pw_set(USERNAME, PASSWORD)

if CA_CERT:
    client.tls_set(ca_certs=CA_CERT)
else:
    client.tls_set()   # use system trust store for production

client.connect(HOST, PORT, keepalive=60)
client.loop_forever()

The PASSWORD listener uses server-side TLS (not mutual TLS). The PLC must trust the Voke CA root or a system-trusted CA that signed the broker certificate. For local dev pass the CA certificate from docker/mosquitto/certs/ca/ca.crt explicitly.


ACL scope

Every PASSWORD plant gets its own explicit ACL block written by MqttPasswordService:

user plc-a1b2c3d4
topic write cpi/a1b2c3d4-0000-0000-0000-000000000001/telemetry
topic write cpi/a1b2c3d4-0000-0000-0000-000000000001/status
topic write cpi/a1b2c3d4-0000-0000-0000-000000000001/ack
topic read  cpi/a1b2c3d4-0000-0000-0000-000000000001/commands

Note that the ACL topics use the full plant UUID (not the short username). The short username is only the MQTT identity; Voke maps it to the plant UUID internally.

No cross-plant access is possible. Attempting to publish to another plant's topic results in an immediate disconnect.


Rotating credentials

Call the provision endpoint again. Voke will:

  1. Generate a new password.
  2. Atomically replace the existing entry in /mosquitto/config/passwd-devices.
  3. Send SIGHUP to Mosquitto.

The old password stops working as soon as Mosquitto reloads (within seconds of the SIGHUP). The ACL block is not changed — only the password hash is updated.

# Re-provision to rotate the password
POST /api/v1/plants/{plantId}/provision

The plant must be in PENDING status to provision. If the plant is already ACTIVE, revoke it first (POST /api/v1/plants/{plantId}/revoke) and then re-provision. Revocation removes the old ACL block and password entry before the new credentials are written.


Troubleshooting

SymptomLikely cause
CONNACK rc=5 — bad credentialsWrong username or password; re-provision and copy the new values
CONNACK rc=5 — connection refusedPlant not provisioned yet, or provisioning failed
SSL certificate verify failedMissing or wrong CA cert; pass ca_certs in tls_set()
Password reloaded but PLC still rejectedMosquitto SIGHUP may have failed; check docker logs cpi-mosquitto
Can publish to telemetry but not read commandsACL entry missing; re-provision to regenerate the ACL block

Next steps

On this page