Custom app fails to send cloud-to-device messages after app enablement policy update

Our custom application that sends cloud-to-device commands stopped working after we updated our app enablement policies in Azure AD. The app registration permissions look correct, but we’re getting 403 Forbidden when trying to send C2D messages through the IoT Hub REST API.


POST https://{hub}.azure-devices.net/messages/devicebound
HTTP/1.1 403 Forbidden
{"Message":"ErrorCode:IotHubUnauthorized;App not authorized"}

The app has ‘IoT Hub Data Sender’ role assigned at the hub level. This was working perfectly before the policy changes. Anyone dealt with app enablement policy conflicts affecting IoT Hub operations?

I’ve worked through this exact scenario multiple times. Here’s the complete solution addressing all three focus areas:

App Registration Permissions: The app enablement policy changes require you to reconfigure your app registration. First, ensure these API permissions are explicitly granted:

  1. Go to Azure AD → App registrations → Your app → API permissions
  2. Add ‘Azure IoT Hub’ API (if not present)
  3. Select ‘user_impersonation’ delegated permission OR ‘IoTHub.ServiceConnect’ application permission
  4. Click ‘Grant admin consent for [tenant]’

The critical step is re-consenting after the policy update. The policy change invalidates previous consent grants.

IoT Hub Data Sender Role: Your role assignment is correct, but you need to verify the authentication context. Update your app’s token request:

var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var tokenRequestContext = new TokenRequestContext(
    new[] { "https://iothubs.azure.net/.default" }
);
var token = await credential.GetTokenAsync(tokenRequestContext);

The scope MUST be ‘https://iothubs.azure.net/.default’ - not the generic Azure Management scope. This is often missed after policy updates.

App Enablement Policy Changes: Your updated policy likely includes conditional access requirements that weren’t there before. Here’s how to fix it:

  1. In Azure AD, go to Enterprise applications → Your app → Conditional Access
  2. Check if there are new policies applied to your app
  3. If a policy requires user assignment, add your app’s service principal to the policy’s exclusion list
  4. Alternatively, create a specific policy exception for IoT Hub service-to-service calls

For the REST API call, ensure your Authorization header uses the correctly scoped token:


Authorization: Bearer {token-from-iothubs-scope}
Content-Type: application/json

The most common mistake is using a token acquired with the wrong scope. The app enablement policy validates both the app identity AND the resource scope in the token claims.

Additional Troubleshooting: If you’re still seeing 403 after these changes:

  1. Enable sign-in logs in Azure AD to see the exact policy rule blocking the request
  2. Check if your policy requires MFA or device compliance - these can’t be satisfied by service principals
  3. Verify the app’s service principal wasn’t disabled during the policy update
  4. Test with a user-delegated token first to isolate whether it’s an app-only permission issue

The policy evaluation chain is: App enablement policy → Conditional access → RBAC → Resource-specific permissions. Your issue is at the first gate, so fixing the app enablement policy configuration should resolve it immediately.

No, hub-level assignment is fine. The problem is more likely in the app registration itself. After updating app enablement policies, you often need to re-consent the application permissions. Go to your app registration in Azure AD, navigate to API permissions, and click ‘Grant admin consent’ again. The policy change might have revoked the previous consent.

Check if your app enablement policy is restricting service-to-service authentication. The IoT Hub Data Sender role is correct, but the policy might be blocking the OAuth token acquisition or limiting which apps can call IoT Hub APIs.

Also verify that your app is using the correct authentication scope when requesting tokens. For IoT Hub C2D operations, the scope should be ‘https://iothubs.azure.net/.default’. If your app is requesting a different scope, the policy might reject it even with proper role assignments.