Skip to content

Device Provisioning Guide

Provisioning is the process of creating device records and generating MQTT credentials so physical devices can authenticate with the broker. Django DeviceHub provides three provisioning methods: programmatic, management command, and REST API.

How Credentials Work

Each device has three authentication fields on the generated Device model (inherited from BaseDevice):

Field Purpose Storage
mqtt_username MQTT login username Plaintext (formatted as {app_label}-{device_id})
mqtt_password_hash MQTT login password Hashed via Django's make_password()
api_key REST API authentication Plaintext hex token (64 chars)

When generate_credentials() is called, it:

  1. Sets mqtt_username to {model_label}-{device_id} (e.g., myapp-weatherstationdevice-STATION-001)
  2. Generates a random 32-byte URL-safe password via secrets.token_urlsafe(32)
  3. Hashes the password with Django's make_password() and stores the hash
  4. Generates a random 64-character hex API key via secrets.token_hex(32)
  5. Returns a dict with the plaintext values

The plaintext MQTT password is only available at generation time. It cannot be retrieved later -- only verified via check_mqtt_password().

Programmatic Provisioning

Single device

from myapp.models import WeatherStationDevice

device = WeatherStationDevice.objects.create(
    device_id="STATION-001",
    name="Field Station Alpha",
)

# Generate credentials (returns plaintext, stores hashed)
credentials = device.generate_credentials()
device.save()

print(credentials)
# {
#     "mqtt_username": "myapp-weatherstationdevice-STATION-001",
#     "mqtt_password": "dG9rZW4tdXJsc2FmZS0zMi1ieXRlcw...",
#     "api_key": "a1b2c3d4e5f6..."
# }

With signal notification

The device_provisioned signal fires after provisioning, allowing you to hook in additional logic:

from django_devicehub.signals import device_provisioned

@receiver(device_provisioned)
def on_provisioned(sender, device, credentials, **kwargs):
    # Log credentials to a secure vault
    vault.store(device.device_id, credentials)

    # Send credentials to a provisioning service
    flash_device(device.device_id, credentials)

Verifying credentials

device = WeatherStationDevice.objects.get(device_id="STATION-001")

# Check MQTT password
if device.check_mqtt_password("the-plaintext-password"):
    print("Password is correct")

# Check API key (stored in plaintext)
if device.api_key == submitted_api_key:
    print("API key is correct")

Bulk Provisioning via Management Command

The iot_provision command creates multiple devices and outputs their credentials:

python manage.py iot_provision weatherstation --count 10

Output (JSON to stdout):

{
  "device_type": "WeatherStation",
  "count": 10,
  "devices": [
    {
      "device_id": "weatherstation-001",
      "name": "WeatherStation 001",
      "device_type": "WeatherStation",
      "mqtt_username": "myapp-weatherstationdevice-weatherstation-001",
      "mqtt_password": "dG9rZW4t...",
      "api_key": "a1b2c3d4..."
    },
    ...
  ]
}

Command options

Option Default Description
device_type (required) Name of a registered DeviceType (case-insensitive)
--count 1 Number of devices to create
--prefix "" Prefix for device IDs
--output (stdout) Write credentials to a JSON file

Saving to file

python manage.py iot_provision weatherstation \
    --count 50 \
    --prefix "farm-" \
    --output credentials.json

This creates devices with IDs like farm-weatherstation-001 through farm-weatherstation-050 and writes all credentials to credentials.json.

Device ID format

The command generates device IDs in the format {prefix}{type_name}-{NNN}:

  • type_name is the DeviceType class name in lowercase
  • NNN is a zero-padded sequential number
  • The starting index is determined by counting existing devices with the same prefix

Existing devices are skipped (not overwritten).

Security warning

The command outputs plaintext credentials. Store them securely:

# Write to file with restricted permissions
python manage.py iot_provision weatherstation --count 10 --output creds.json
chmod 600 creds.json

REST API Provisioning

If AUTO_API is enabled (the default) and Django REST Framework is installed, each device type gets a /provision/ action on its ViewSet:

POST /api/things/weatherstation/devices/{id}/provision/

Response:

{
    "device_id": "STATION-001",
    "credentials": {
        "mqtt_username": "myapp-weatherstationdevice-STATION-001",
        "mqtt_password": "dG9rZW4t...",
        "api_key": "a1b2c3d4..."
    },
    "topics": {
        "data": "myproject/weatherstation/STATION-001/data",
        "status": "myproject/weatherstation/STATION-001/status",
        "commands": "myproject/weatherstation/STATION-001/cmd"
    }
}

The response includes the MQTT topics the device should publish to and subscribe to, making it easy to configure the device firmware.

The device_provisioned signal fires after the API call.

Credential Rotation

To rotate credentials for an existing device, call generate_credentials() again:

device = WeatherStationDevice.objects.get(device_id="STATION-001")
new_credentials = device.generate_credentials()
device.save()

This overwrites the previous mqtt_username, mqtt_password_hash, and api_key. The device will need to be updated with the new credentials.

For bulk rotation:

for device in WeatherStationDevice.objects.filter(is_active=True):
    creds = device.generate_credentials()
    device.save()
    # Store or distribute new credentials
    distribute_credentials(device.device_id, creds)

API Key Authentication

The DeviceAuthBackend supports two authentication methods:

MQTT username/password

Used by the broker auth endpoint (/iot/broker/auth/):

from django_devicehub.auth.backends import DeviceAuthBackend

device = DeviceAuthBackend.authenticate("mqtt-username", "mqtt-password")
if device:
    print(f"Authenticated: {device.device_id}")

API key

Used for REST API device authentication:

device = DeviceAuthBackend.authenticate_api_key("a1b2c3d4...")
if device:
    print(f"Authenticated: {device.device_id}")

Both methods search across all registered device models, so they work regardless of which DeviceType the device belongs to. Only active devices (is_active=True) can authenticate.

Device Lifecycle

A typical provisioning workflow:

  1. Create the device record (via admin, API, or management command)
  2. Provision credentials with generate_credentials()
  3. Flash the credentials onto the physical device
  4. Connect -- the device authenticates via MQTT, the broker calls the auth endpoint, and the device is marked online
  5. Operate -- the device publishes data and status messages
  6. Rotate credentials periodically for security
  7. Deactivate -- set is_active=False to block a device without deleting it

Device status values

Status Description
online Device is connected and sending data
offline Device is disconnected
error Device reported an error
maintenance Device is under maintenance
low_battery Device battery is low
provisioning Device has been created but not yet connected