The Device Application
In this post, we’ll walk through the complete IoT device application that uses DPS provisioning with CSR-based certificate issuance. Rather than building from scratch, if you’ve not already done so, we’ll clone the repository, run the setup automation, configure the application, and explore how the major components work together.
Contents:
- Introduction
- A DPS Primer
- Creating DPS, IoT Hub and ADR Instances
- Understanding X.509 and CSR Workflows
- The Device Application (This Post)
Why a Customer DPS Framework?
Microsoft’s official C# SDK doesn’t yet support the
2025-07-01-previewAPI required for CSR-based certificate issuance. I’ve built a custom MQTT-based DPS framework that handles the preview API features.Note: While these features are in preview, It’s super important that you don’t try to use the Azure Portal to remove your resources.
For now I’ve created a couple of scripts to help you remove your resources, you’ll find them at the end of this post.
Application Architecture
┌─────────────────────────────────────────────────────────┐
│ Program.cs │
│ (Main Entry Point) │
└─────────────────────┬───────────────────────────────────┘
│
┌─────────────┬───┴─────────┬───────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐
│ Config │ │ DPS │ │Telemetry │ │ Hardware │
│ Loader │ │ Init │ │ Service │ │ Drivers │
└─────────┘ └────┬─────┘ └────┬─────┘ └──────────────┘
│ │
▼ ▼
┌─────────────────────────┐
│ Azure DPS Framework │
│ (Custom MQTT + CSR) │
└──────────┬──────────────┘
│
▼
┌─────────────┐
│ IoT Hub │
└─────────────┘
Getting Started
Already completed previous posts?
If you’ve already cloned the repository and run the setup scripts from the earlier posts, you can skip to Step 3: Configure the Application.
The steps below are for those starting fresh or who need to set up the complete environment.
Step 1: Clone the Repository (If Not Already Done)
If you haven’t already cloned the repository from earlier posts, get the complete project code:
Clone the repository:
git clone https://github.com/pjgpetecodes/skittlesorter.git
cd skittlesorterStep 2: Run Full Azure Setup (If Not Already Done)
If you haven’t already run the Azure setup automation from Post 02, run the comprehensive setup script to create all Azure resources, certificates, and enrollment groups:
Set your parameters:
$resourceGroup = "my-iot-rg"
$location = "eastus"
$iotHubName = "my-iothub-001"
$dpsName = "my-dps-001"
$adrNamespace = "my-adrnamespace-001"
$userIdentity = "my-uami"
$registrationId = "my-device"Run the full setup script:
cd scripts
.\setup-x509-dps-adr.ps1 `
-ResourceGroup $resourceGroup `
-Location $location `
-IoTHubName $iotHubName `
-DPSName $dpsName `
-AdrNamespace $adrNamespace `
-UserIdentity $userIdentity `
-RegistrationId $registrationIdThis script will:
- Create resource group, IoT Hub, DPS, and ADR namespace
- Generate root CA, intermediate CA, and device bootstrap certificates
- Upload and verify certificates with DPS
- Create enrollment group with credential policy
- Link all services together
Save the output values:
- IdScope: From DPS (format:
0ne00XXXXXX) - Hub Hostname: Your IoT Hub endpoint
- Certificate Paths: Location of device certificates
Step 3: Configure the Application
Update appsettings.json with your values from the setup script:
{
{
"Hardware": {
"MockMode": {
"EnableMockColorSensor": true,
"EnableMockServos": true,
"MockColorSequence": [
"Red",
"Green",
"Yellow",
"Purple",
"Orange",
"Red",
"Green"
]
},
...
}
"IoTHub": {
"DpsProvisioning": {
"ProvisioningHost": "global.azure-devices-provisioning.net",
"IdScope": "YOUR_DPS_ID_SCOPE",
"RegistrationId": "YOUR_DEVICE_REGISTRATION_ID",
"AttestationMethod": "SymmetricKey",
"EnrollmentGroupKeyBase64": "YOUR_ENROLLMENT_GROUP_PRIMARY_KEY",
"AttestationCertPath": "scripts/certs/device/{RegistrationId}-device.pem",
"AttestationKeyPath": "scripts/certs/device/{RegistrationId}-device.key",
"AttestationCertChainPath": "scripts/certs/ca/{RegistrationId}-chain.pem",
"CsrFilePath": "certs/device/{RegistrationId}-device.csr",
"CsrKeyFilePath": "certs/device/{RegistrationId}-device.key",
"IssuedCertFilePath": "certs/issued/{RegistrationId}-issued.pem",
"ApiVersion": "2025-07-01-preview",
"SasExpirySeconds": 3600,
"AutoGenerateCsr": true,
"MqttPort": 8883,
"EnableDebugLogging": true
},
"DeviceId": "my-device",
"SendTelemetry": true,
"TelemetryIntervalMs": 5000
},
"Adr": {
"Enabled": true,
"SubscriptionId": "<YOUR_SUBSCRIPTION_KEY>",
"ResourceGroupName": "<YOUR_RESOURCE_GROUP",
"NamespaceName": "<YOUR_ADR_NAMESPACE_NAME"
}
}Key Configuration Sections:
- DpsProvisioning: Bootstrap certificate paths and DPS connection details
- DeviceId: Must match your registration ID
- IssuedCertPath: Where the operational certificate from DPS will be saved
- Mock: Enable hardware simulation for testing without physical devices
Step 4: Run the Application
Build and run:
cd ..
dotnet runExpected output:
Skittle Sorter Starting...
=== DPS Configuration ===
IdScope: 0ne00XXXXXX
RegistrationId: my-device
AttestationMethod: X509
=== Starting DPS Provisioning ===
Loaded bootstrap certificate: CN=my-device
Generated new RSA key pair for operational certificate.
Submitting registration request to DPS...
✓ Device assigned to: my-iothub-001.azure-devices.net
✓ Device ID: my-device
=== Saving Issued Certificate ===
Certificate Subject: CN=my-device
Issuer: CN=Microsoft-Managed-ICA-...
Valid from: 2026-02-07 10:00:00Z
Valid until: 2026-03-09 10:00:00Z
✓ Certificate saved to: ./certs/issued/device.pfx
=== Connecting to IoT Hub ===
Connecting to: my-iothub-001.azure-devices.net
Device ID: my-device
✓ Connected to IoT Hub
=== Starting Sorting Loop ===
Detected: red (R:255 G:50 B:50 C:1200)
→ Telemetry sent: red (count: 1)
✓ red sorted
Detected: green (R:50 G:255 B:50 C:1180)
→ Telemetry sent: green (count: 1)
✓ green sorted
What’s happening:
- DPS Configuration – Loads settings from
appsettings.json - DPS Provisioning – Authenticates with bootstrap certificate and generates CSR
- Saving Certificate – Receives operational certificate from DPS and saves it locally
- Connecting to IoT Hub – Uses operational certificate to establish MQTT connection
- Sorting Loop – Processes skittles and sends telemetry
Verify in the Portal
You should now be able to navigate to your resource group in the Azure Portal and see your device happily registered and showing as a first class citizen;

If you Navigate to your IoT Hub, and then to Device Management, and Devices, you should see your traditional IoT Hub Device present too;

Setting Up More Devices
Should you want to set up more devices, this is how you go about provisioning them.
Navigate to the Scripts folder and run the setup script with the following parameters;
cd ./scripts
./setup-x509-attestation.ps1 -RegistrationId "<new-device-id>" -ReuseCa -CaRegistrationId "skittlesorter" -SkipEnrollmentThen, you can copy the following three files to your device to the scripts/certs folder;
scripts/certs/device/<new-device-id>-device.pemscripts/certs/device/<new-device-id>-device.keyscripts/certs/ca/<new-device-id>-chain.pem
Then, along with your IdScope and other related settings, you need to modify the device’s appsettings.json with the following values;
RegistrationId = "<new-device-id>"AttestationCertPathto<new-device-id>-device.pemAttestationKeyPathto<new-device-id>-device.keyAttestationCertChainPathto<new-device-id>-chain.pem
Finally, you can start the application, and you should have a newly provisioning device in your DPS, IoT Hub and in ADR along with the portal arm resource.

Troubleshooting Common Issues
Problem: “Failed to load bootstrap certificate”
Error: The system cannot find the file specified.
at System.Security.Cryptography.X509Certificates.X509Certificate2..ctorSolution:
- Verify
AttestationCertPathinappsettings.jsonpoints to correct location - Check if you ran the setup script from the
scripts/directory - Ensure certificates exist:
./scripts/certs/device/device.pem
Check certificate files:
Test-Path "./scripts/certs/device/<YOUR_DEVICE_REGISTRATION_ID>-device.pem"
Test-Path "./scripts/certs/device/<YOUR_DEVICE_REGISTRATION_ID>-device.key"Problem: “DPS registration failed: Unauthorized”
DPS initialization failed: Registration failed with status: UnauthorizedSolution:
- Verify your enrollment group exists in DPS
- Check that CA certificates are uploaded and verified in DPS (see Post 03)
- Confirm
IdScopeinappsettings.jsonmatches DPS
Verify enrollment group:
az iot dps enrollment-group show `
--dps-name "my-dps-001" `
--resource-group "my-iot-rg" `
--enrollment-id "my-device-group"Verify CA certificates are verified:
az iot dps certificate show `
--dps-name "my-dps-001" `
--resource-group "my-iot-rg" `
--certificate-name "my-device-root" `
--query "properties.isVerified"Problem: “Failed to connect to IoT Hub”
Error: The remote certificate is invalid according to the validation procedureSolution:
- Check that DPS is linked to IoT Hub
- Verify IoT Hub hostname matches assigned hub
- Ensure operational certificate was saved correctly
Verify DPS linked hub:
az iot dps linked-hub list `
--dps-name "my-dps-001" `
--resource-group "my-iot-rg"Problem: “Device already provisioned” but can’t connect
If you see the operational certificate exists but connection fails:
Delete and re-provision:
Remove-Item "./certs/issued/device.pfx" -ErrorAction SilentlyContinue
dotnet runProblem: Missing appsettings.json configuration
Unhandled exception. System.Exception: IoTHub config missingSolution:
- Ensure
appsettings.jsonexists in project root - Copy from
appsettings.x509.jsontemplate if needed:
Copy-Item "appsettings.x509.json" "appsettings.json"Edit with your values:
notepad appsettings.jsonProblem: Certificate expired or renewal needed
Certificate Valid until: 2026-01-15 10:00:00Z (expired)Solution:
- Delete expired certificate and re-provision
- Operational certificates are short-lived (30 days default)
- Application should auto-renew within 7-day window
Force renewal:
Remove-Item "./certs/issued/device.pfx" -ErrorAction SilentlyContinue
dotnet runStill having issues?
- Check Azure resources are properly configured (Post 02)
- Verify certificates were created and verified (Post 03)
- Review enrollment group configuration (Post 04)
- Check DPS and IoT Hub logs in Azure Portal
- Enable verbose logging in
appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Information"
}
}
}Understanding the Code Architecture
Now let’s explore the major components that make this work.
Project Structure
skittlesorter/
├── src/
│ ├── Program.cs # Main entry point
│ ├── comms/
│ │ ├── DpsInitializationService.cs # DPS provisioning logic
│ │ └── TelemetryService.cs # IoT Hub telemetry
│ ├── configuration/
│ │ └── ConfigurationLoader.cs # Load appsettings.json
│ └── drivers/
│ ├── SkittleSorterService.cs # Main business logic
│ ├── TCS3472x.cs # Color sensor driver
│ └── ServoController.cs # Servo control
├── AzureDpsFramework/ # Custom DPS framework
│ ├── ProvisioningDeviceClient.cs
│ ├── DpsConfiguration.cs
│ ├── SecurityProvider*.cs
│ ├── Transport/
│ │ └── ProvisioningTransportHandlerMqtt.cs
│ └── Adr/
│ └── AdrDeviceRegistryClient.cs
└── appsettings.json # Device configurationMain Program Flow (src/Program.cs)
The main program orchestrates the device lifecycle:
static async Task Main(string[] args)
{
// 1. Load configuration
var iotConfig = ConfigurationLoader.LoadIoTHubConfiguration();
var mockConfig = ConfigurationLoader.LoadMockConfiguration();
// 2. Initialize hardware (mocked or real)
using var colorSensor = mockConfig.EnableMockColorSensor
? new TCS3472x(true, mockConfig.MockColorSequence)
: new TCS3472x();
var servo = new ServoController(mockConfig.EnableMockServos);
// 3. Initialize DPS and connect to IoT Hub
DeviceClient? deviceClient = DpsInitializationService.Initialize(iotConfig);
// 4. Create telemetry service
TelemetryService? telemetryService = null;
if (deviceClient != null)
{
telemetryService = new TelemetryService(deviceClient, iotConfig.DeviceId);
}
// 5. Main sorting loop
while (true)
{
// Pick, scan, sort, and report telemetry
servo.MoveToPickPosition();
var (clear, red, green, blue) = colorSensor.ReadColor();
string color = colorSensor.ClassifySkittleColor(red, green, blue, clear);
if (telemetryService != null)
{
await telemetryService.SendSkittleColorTelemetryAsync(color);
}
servo.MoveToColorChute(color);
}
}Key responsibilities:
- Configuration loading – Reads
appsettings.jsonfor DPS, IoT Hub, and hardware settings - Hardware initialization – Sets up color sensor and servo controllers (real or mocked)
- DPS provisioning – Calls our custom framework to get operational certificate
- IoT Hub connection – Establishes MQTT connection using X.509 certificate
- Business logic – Runs the main sorting loop with telemetry reporting
DPS Initialization Service (src/comms/DpsInitializationService.cs)
This service handles the complete provisioning workflow:
Phase 1: Check for existing certificate
if (File.Exists(dpsCfg.IssuedCertPath))
{
Console.WriteLine("Device already provisioned. Using existing certificate.");
return ConnectToIoTHub(dpsCfg, iotConfig);
}Phase 2: Provision device with bootstrap credentials
// Load bootstrap certificate
var bootstrapCert = new X509Certificate2(
dpsCfg.AttestationCertPath,
dpsCfg.AttestationCertPassword
);
// Generate new key for operational certificate
RSA rsa = RSA.Create(2048);
// Create security provider
var security = new SecurityProviderX509CsrWithCert(
dpsCfg.RegistrationId,
bootstrapCert,
rsa
);
// Create provisioning client
var client = ProvisioningDeviceClient.Create(
dpsCfg.ProvisioningHost,
dpsCfg.IdScope,
security,
new ProvisioningTransportHandlerMqtt()
);
// Register device
var result = client.RegisterAsync().GetAwaiter().GetResult();Phase 3: Save issued certificate
// Parse issued certificate from DPS response
byte[] certBytes = Convert.FromBase64String(result.IssuedCertificateChain[0]);
var certificate = new X509Certificate2(certBytes);
// Combine with private key
var certWithKey = certificate.CopyWithPrivateKey(rsa);
// Export to PFX for storage
byte[] pfx = certWithKey.Export(X509ContentType.Pfx, dpsCfg.IssuedCertPassword);
File.WriteAllBytes(dpsCfg.IssuedCertPath, pfx);Phase 4: Connect to IoT Hub
var certificate = new X509Certificate2(
dpsCfg.IssuedCertPath,
dpsCfg.IssuedCertPassword
);
var auth = new DeviceAuthenticationWithX509Certificate(
iotConfig.DeviceId,
certificate
);
var deviceClient = DeviceClient.Create(
assignedHub,
auth,
TransportType.Mqtt
);Custom DPS Framework (AzureDpsFramework/)
Why I built it:
Microsoft’s official C# Device Provisioning SDK doesn’t support the 2025-07-01-preview API version required for CSR-based certificate issuance. The preview features include:
- Certificate Signing Request (CSR) submission during registration
- Receiving issued certificates in the registration response
- Microsoft-managed PKI via Azure Device Registry
What it does:
My custom framework implements the preview API using raw MQTT communication:
1. Security Providers (SecurityProvider*.cs)
// SecurityProviderX509CsrWithCert.cs
public class SecurityProviderX509CsrWithCert : SecurityProvider
{
private readonly X509Certificate2 _bootstrapCertificate;
private readonly RSA _operationalKey;
public SecurityProviderX509CsrWithCert(
string registrationId,
X509Certificate2 bootstrapCert,
RSA operationalKey)
{
_bootstrapCertificate = bootstrapCert;
_operationalKey = operationalKey;
}
public string GenerateCsr()
{
// Create Certificate Signing Request
var request = new CertificateRequest(
$"CN={RegistrationId}",
_operationalKey,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1
);
return Convert.ToBase64String(request.CreateSigningRequest());
}
}This generates a CSR using the new operational key pair, not the bootstrap certificate’s key.
2. MQTT Transport Handler (ProvisioningTransportHandlerMqtt.cs)
public async Task<DeviceRegistrationResult> RegisterAsync(
string idScope,
SecurityProvider security)
{
// Connect to DPS with bootstrap credentials
var mqttClient = await ConnectAsync(security);
// Subscribe to response topics
await mqttClient.SubscribeAsync("$dps/registrations/res/#");
// Build registration payload with CSR
var payload = new
{
registrationId = security.RegistrationId,
certificateSigningRequest = security.GenerateCsr()
};
// Publish registration request
await mqttClient.PublishAsync(
$"$dps/registrations/PUT/iotdps-register/?$rid={operationId}&api-version=2025-07-01-preview",
JsonSerializer.Serialize(payload)
);
// Wait for response
var response = await WaitForResponseAsync(mqttClient, operationId);
// Parse issued certificate from response
return new DeviceRegistrationResult
{
AssignedHub = response.AssignedHub,
DeviceId = response.DeviceId,
IssuedCertificateChain = response.IssuedCertificateChain
};
}Key features:
- Uses preview API version (
2025-07-01-preview) - Includes CSR in registration payload
- Extracts issued certificate from response
- Handles MQTT QoS and response correlation
3. Provisioning Client (ProvisioningDeviceClient.cs)
public class ProvisioningDeviceClient
{
public static ProvisioningDeviceClient Create(
string provisioningHost,
string idScope,
SecurityProvider security,
ProvisioningTransportHandler transport)
{
return new ProvisioningDeviceClient(
provisioningHost,
idScope,
security,
transport
);
}
public async Task<DeviceRegistrationResult> RegisterAsync()
{
// Delegate to transport handler
return await _transport.RegisterAsync(_idScope, _security);
}
}Telemetry Service (src/comms/TelemetryService.cs)
Handles sending device telemetry to IoT Hub:
public async Task SendSkittleColorTelemetryAsync(string color)
{
var telemetry = new
{
deviceId = _deviceId,
timestamp = DateTime.UtcNow,
color = color,
count = _colorCounts.GetValueOrDefault(color, 1)
};
string json = JsonSerializer.Serialize(telemetry);
var message = new Message(Encoding.UTF8.GetBytes(json))
{
ContentType = "application/json",
ContentEncoding = "utf-8"
};
message.Properties.Add("skittleColor", color);
message.Properties.Add("deviceType", "skittle-sorter");
await _deviceClient.SendEventAsync(message);
}Features:
- JSON-formatted telemetry messages
- Custom message properties for routing
- Per-color count tracking
- Batch summary reporting
Certificate Renewal
The application monitors certificate expiration and triggers renewal:
public static async Task CheckCertificateRenewal(DpsConfiguration dpsCfg)
{
var cert = new X509Certificate2(
dpsCfg.IssuedCertPath,
dpsCfg.IssuedCertPassword
);
// Check if within renewal window (7 days before expiry)
var renewalWindow = cert.NotAfter.AddDays(-7);
if (DateTime.UtcNow >= renewalWindow)
{
Console.WriteLine("Certificate renewal window reached. Re-provisioning...");
// Delete old certificate
File.Delete(dpsCfg.IssuedCertPath);
// Trigger re-provisioning (same flow as initial)
}
}Azure Device Registry (ADR) Integration
Azure Device Registry provides device identity and metadata management that works with DPS and IoT Hub.
Key ADR Features
🔹 Device Metadata
- Attributes (hardware ID, model, version)
- Tags (location, tenant, environment)
- Custom properties
🔹 Credential Management
- Credential policies for certificate issuance
- Automated lifecycle management
- Certificate rotation
🔹 Multi-Hub Support
- Device identity independent of IoT Hub
- Move devices between hubs without re-provisioning
- Query devices across multiple hubs
ADR Client Implementation
The project includes an ADR client for device management (AzureDpsFramework/Adr/AdrDeviceRegistryClient.cs):
using Azure.Identity;
using AzureDpsFramework.Adr;
// Create ADR client (uses DefaultAzureCredential)
var adrClient = new AdrDeviceRegistryClient();
// List all devices in namespace
var devices = await adrClient.ListDevicesAsync(
subscriptionId: "your-subscription-id",
resourceGroupName: "my-iot-rg",
namespaceName: "my-adrnamespace-001"
);
Console.WriteLine($"Found {devices.Count} devices:");
foreach (var device in devices)
{
Console.WriteLine($" - {device.Name} ({device.Properties?.Enabled ?? false})");
}
// Get specific device details
var deviceDetails = await adrClient.GetDeviceAsync(
subscriptionId: "your-subscription-id",
resourceGroupName: "my-iot-rg",
namespaceName: "my-adrnamespace-001",
deviceName: "my-device"
);
if (deviceDetails != null)
{
Console.WriteLine($"\nDevice: {deviceDetails.Name}");
Console.WriteLine($"Enabled: {deviceDetails.Properties?.Enabled}");
Console.WriteLine($"Hardware ID: {deviceDetails.Properties?.Attributes?.HardwareId}");
// Display tags
if (deviceDetails.Properties?.Tags != null)
{
Console.WriteLine("Tags:");
foreach (var tag in deviceDetails.Properties.Tags)
{
Console.WriteLine($" {tag.Key}: {tag.Value}");
}
}
}Configuration-Driven ADR Updates
Enable automatic device metadata updates in appsettings.json:
{
"Adr": {
"Enabled": true,
"SubscriptionId": "your-subscription-id",
"ResourceGroupName": "my-iot-rg",
"NamespaceName": "my-adrnamespace-001",
"DeviceUpdate": {
"Enabled": true,
"Attributes": {
"HardwareId": "RPI4B-12345678",
"Manufacturer": "Raspberry Pi Foundation",
"Model": "Raspberry Pi 4B",
"OperatingSystemVersion": "Raspbian GNU/Linux 11"
},
"Tags": {
"location": "factory-1",
"department": "manufacturing",
"firmware": "1.0.5"
}
}
}
}The application automatically updates device metadata after provisioning.
Testing the Complete Solution
Test 1: Verify Device in IoT Hub
Check device exists:
az iot hub device-identity show `
--hub-name "my-iothub-001" `
--device-id "my-device"Expected output:
{
"deviceId": "my-device",
"authentication": {
"type": "certificateAuthority"
},
"connectionState": "Connected",
"status": "enabled"
}Test 2: Monitor Telemetry
Watch device-to-cloud messages:
az iot hub monitor-events `
--hub-name "my-iothub-001" `
--device-id "my-device"Expected output:
{
"event": {
"origin": "my-device",
"payload": {
"deviceId": "my-device",
"timestamp": "2026-02-07T12:34:56Z",
"color": "red",
"count": 1
}
}
}Test 3: Query ADR Devices
List devices:
az iot ops asset endpoint device list `
--namespace "my-adrnamespace-001" `
--resource-group "my-iot-rg" `
--output tableExpected output:
Name Enabled HardwareId Location
---------- --------- ----------------- ----------
my-device True RPI4B-12345678 factory-1Test 4: Device Twin Operations
Update desired properties:
az iot hub device-twin update `
--hub-name "my-iothub-001" `
--device-id "my-device" `
--set properties.desired='{"telemetryInterval":3000,"enableSorting":true}'The device receives the update and responds with reported properties.
Production Deployment Checklist
✅ Security
- [ ] Store enrollment keys in Azure Key Vault
- [ ] Use hardware security modules (TPM) for private keys
- [ ] Enable certificate pinning
- [ ] Implement certificate revocation checks
- [ ] Use separate enrollment groups for dev/staging/prod
- [ ] Rotate enrollment group keys periodically
✅ Reliability
- [ ] Implement exponential backoff for retries
- [ ] Handle network disconnections gracefully
- [ ] Monitor certificate expiration
- [ ] Implement automatic certificate renewal
- [ ] Log all provisioning events
- [ ] Set up alerting for failed provisioning
✅ Monitoring
- [ ] Enable Azure Monitor for IoT Hub
- [ ] Configure diagnostic logs for DPS
- [ ] Track device connection metrics
- [ ] Monitor telemetry delivery rates
- [ ] Set up alerts for certificate expiration
- [ ] Track ADR API usage and errors
✅ Configuration
- [ ] Use environment-specific appsettings files
- [ ] Store secrets in Key Vault or environment variables
- [ ] Document all configuration parameters
- [ ] Validate configuration on startup
- [ ] Implement configuration hot-reload
✅ Testing
- [ ] Unit tests for CSR generation
- [ ] Integration tests for DPS provisioning
- [ ] E2E tests for telemetry flow
- [ ] Load testing for concurrent provisioning
- [ ] Certificate renewal testing
Removing your Resources and Clean Test Start Script
Note: While these features are in preview, It’s super important that you don’t try to use the Portal to remove your resources.
For now I’ve created a couple of scripts to help you remove your resources.
Remove Resources:
To remove your resources, it’s important that you remove them in teh right order, otherwise you may find that you have resources such as the ADR Namespace, which cannot be deleted.
You’ll most certainly find this if you try to remove your resources using the portal, as it doesn’t know yet to delete the resources in order.
This script will remove your resources safely.
cd scripts
.\remove-resources.ps1 `
-DpsName "<your-dps-name>" `
-IotHubName "<your-iothub-name>" `
-ResourceGroup "<your-resource-group>" `
-EnrollmentGroupName "<your-enrollment-group-name>" `
-DeviceId "<your-device-id>" `
-AdrNamespace "<your-adr-namespace>" `
-UserIdentity "<your-user-assigned-identity-name>" `
-RootCertName "<your-root-cert-name>" `
-IntermediateCertName "<your-intermediate-cert-name>"What it does:
- Stops running processes
- Deletes issued certificates
- Removes device from IoT Hub
- Removes device from ADR
- Waits for propagation
Run clean test:
cd scripts
.\clean-test-start.ps1 `
-DpsName "<your-dps-name>" `
-IotHubName "<your-iothub-name>" `
-ResourceGroup "<your-resource-group>" `
-EnrollmentGroupName "<your-enrollment-group-name>" `
-DeviceId "<your-device-id>" `
-AdrNamespace "<your-adr-namespace>" `
-RootCertName "<your-root-cert-name>" `
-IntermediateCertName "<your-intermediate-cert-name>" `
-RemoveCaCertsWhat it does:
- Stops running processes
- Deletes issued certificates
- Removes device from IoT Hub
- Removes device from ADR
- Waits for propagation
- Runs application fresh
This ensures clean provisioning from scratch every time.
Key Takeaways
✅ Custom DPS Framework Required – Official SDK doesn’t support preview API
✅ Two-Phase Authentication – Bootstrap cert for DPS, operational cert for IoT Hub
✅ CSR-Based Issuance – Private key never leaves device
✅ MQTT Protocol – Direct protocol implementation for preview features
✅ Certificate Lifecycle – Automatic storage, loading, and renewal logic
✅ Mock Hardware Support – Test without physical devices
What We Accomplished
✅ Cloned the repository and ran full setup automation
✅ Configured the device application with Azure resources
✅ Understood the main program flow and component architecture
✅ Explored the custom DPS framework and why it’s needed
✅ Learned how CSR-based provisioning works end-to-end
✅ Integrated with Azure Device Registry for metadata management
✅ Tested the complete solution with telemetry monitoring
✅ Prepared for production deployment
Further Learning
- Azure IoT Hub Documentation: learn.microsoft.com/azure/iot-hub
- Device Provisioning Service: learn.microsoft.com/azure/iot-dps
- Azure Device Registry: learn.microsoft.com/azure/iot/iot-device-registry-overview
- X.509 Certificates: learn.microsoft.com/azure/iot-hub/iot-hub-x509ca-overview
Questions or Issues?
- Open an issue on GitHub
- Check the Troubleshooting.md guide
- Review Azure Setup documentation
Thank you for following this series! Happy IoT building! 🚀
