Skip to content
Back to Blog
1 min read

Azure IoT Security Best Practices

I wrote “Azure IoT Security Best Practices” to share practical, production-minded guidance on this topic.

Authentication Options

Symmetric Key Authentication

from azure.iot.device import IoTHubDeviceClient

# Simple but requires secure key distribution
device_client = IoTHubDeviceClient.create_from_connection_string(
    "HostName=myhub.azure-devices.net;DeviceId=device001;SharedAccessKey=..."
)
from azure.iot.device import IoTHubDeviceClient, X509

# More secure - certificate-based authentication
x509 = X509(
    cert_file="./device_cert.pem",
    key_file="./device_key.pem"
)

device_client = IoTHubDeviceClient.create_from_x509_certificate(
    hostname="myhub.azure-devices.net",
    device_id="device001",
    x509=x509
)

Generate Device Certificates

# Generate CA certificate
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
    -subj "/CN=MyIoTCA"

# Generate device certificate
openssl genrsa -out device001.key 2048
openssl req -new -key device001.key -out device001.csr \
    -subj "/CN=device001"
openssl x509 -req -in device001.csr -CA ca.crt -CAkey ca.key \
    -CAcreateserial -out device001.crt -days 365

# Verify certificate chain
openssl verify -CAfile ca.crt device001.crt

SAS Token Management

import base64
import hmac
import hashlib
import time
from urllib.parse import quote_plus

class SASTokenGenerator:
    @staticmethod
    def generate_token(uri, key, policy_name=None, expiry=3600):
        """Generate a SAS token for IoT Hub authentication"""
        ttl = int(time.time()) + expiry
        sign_key = f"{quote_plus(uri)}\n{ttl}"

        signature = base64.b64encode(
            hmac.new(
                base64.b64decode(key),
                sign_key.encode('utf-8'),
                hashlib.sha256
            ).digest()
        ).decode('utf-8')

        token = f"SharedAccessSignature sr={quote_plus(uri)}&sig={quote_plus(signature)}&se={ttl}"

        if policy_name:
            token += f"&skn={policy_name}"

        return token

# Generate device token
token = SASTokenGenerator.generate_token(
    uri="myhub.azure-devices.net/devices/device001",
    key="device_symmetric_key",
    expiry=86400  # 24 hours
)

Network Security

Private Endpoints

# Create private endpoint for IoT Hub
az network private-endpoint create \
    --name myIoTHubPrivateEndpoint \
    --resource-group myResourceGroup \
    --vnet-name myVNet \
    --subnet mySubnet \
    --private-connection-resource-id "/subscriptions/.../providers/Microsoft.Devices/IotHubs/myIoTHub" \
    --group-id iotHub \
    --connection-name myConnection

# Disable public network access
az iot hub update \
    --name myIoTHub \
    --resource-group myResourceGroup \
    --set properties.publicNetworkAccess=Disabled

IP Filtering

# Allow only specific IP ranges
az iot hub update \
    --name myIoTHub \
    --resource-group myResourceGroup \
    --set properties.ipFilterRules='[
        {
            "filterName": "AllowCorporate",
            "action": "Accept",
            "ipMask": "10.0.0.0/8"
        },
        {
            "filterName": "DenyAll",
            "action": "Reject",
            "ipMask": "0.0.0.0/0"
        }
    ]'

Access Control

Role-Based Access Control

# Create custom role for device operators
az role definition create --role-definition '{
    "Name": "IoT Device Operator",
    "Description": "Can manage devices but not hub settings",
    "Actions": [
        "Microsoft.Devices/IotHubs/devices/read",
        "Microsoft.Devices/IotHubs/devices/write",
        "Microsoft.Devices/IotHubs/devices/delete"
    ],
    "NotActions": [
        "Microsoft.Devices/IotHubs/write",
        "Microsoft.Devices/IotHubs/delete"
    ],
    "AssignableScopes": ["/subscriptions/{subscription-id}"]
}'

# Assign role
az role assignment create \
    --assignee user@domain.com \
    --role "IoT Device Operator" \
    --scope "/subscriptions/.../resourceGroups/.../providers/Microsoft.Devices/IotHubs/myIoTHub"

Shared Access Policies

# Create restricted policy for device management
az iot hub policy create \
    --hub-name myIoTHub \
    --resource-group myResourceGroup \
    --name deviceManager \
    --permissions RegistryRead RegistryWrite

# Create policy for service operations
az iot hub policy create \
    --hub-name myIoTHub \
    --resource-group myResourceGroup \
    --name serviceOperator \
    --permissions ServiceConnect

Secure Device Provisioning

from azure.iot.device.aio import ProvisioningDeviceClient
import os

async def secure_provisioning():
    """Provision device with attestation"""

    # Use TPM attestation for hardware security
    provisioning_client = ProvisioningDeviceClient.create_from_tpm(
        provisioning_host="global.azure-devices-provisioning.net",
        registration_id=os.environ["REGISTRATION_ID"],
        id_scope=os.environ["ID_SCOPE"]
    )

    # Or use X.509 certificate attestation
    from azure.iot.device import X509
    x509 = X509(
        cert_file="./device.crt",
        key_file="./device.key"
    )

    provisioning_client = ProvisioningDeviceClient.create_from_x509_certificate(
        provisioning_host="global.azure-devices-provisioning.net",
        registration_id=os.environ["REGISTRATION_ID"],
        id_scope=os.environ["ID_SCOPE"],
        x509=x509
    )

    result = await provisioning_client.register()
    return result

Data Encryption

from cryptography.fernet import Fernet
import json

class SecureTelemetry:
    def __init__(self, encryption_key):
        self.cipher = Fernet(encryption_key)

    def encrypt_payload(self, data):
        """Encrypt sensitive telemetry data"""
        json_data = json.dumps(data).encode('utf-8')
        return self.cipher.encrypt(json_data)

    def decrypt_payload(self, encrypted_data):
        """Decrypt telemetry data"""
        decrypted = self.cipher.decrypt(encrypted_data)
        return json.loads(decrypted.decode('utf-8'))

# Usage
secure = SecureTelemetry(Fernet.generate_key())

# Encrypt before sending
encrypted = secure.encrypt_payload({
    'temperature': 25.5,
    'patientId': 'confidential'
})

await device_client.send_message(encrypted)

Security Monitoring

from azure.monitor.query import LogsQueryClient
from azure.identity import DefaultAzureCredential

def query_security_events():
    """Query IoT Hub security events"""
    credential = DefaultAzureCredential()
    client = LogsQueryClient(credential)

    query = """
    AzureDiagnostics
    | where ResourceType == "IOTHUBS"
    | where Category == "Connections"
    | where ResultType == "Failure"
    | summarize FailedConnections = count() by DeviceId, bin(TimeGenerated, 1h)
    | order by FailedConnections desc
    """

    response = client.query_workspace(
        workspace_id="your-workspace-id",
        query=query,
        timespan="P1D"
    )

    return response.tables[0].rows

Implementing these security best practices creates a defense-in-depth approach for your IoT solutions.\n\n## Takeaways\n\nAdd a concise, personal takeaway and recommended next steps here.\n

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.