Back to Blog
4 min read

Azure IoT Security Best Practices

Security is paramount in IoT deployments. Azure IoT provides multiple layers of security that, when properly configured, protect your devices, data, and backend services.

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.

Michael John Peña

Michael John Peña

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