public static async Task ModuleIdentityRequest( [EventGridTrigger] EventGridEvent eventGridEvent, ILogger log) { log.LogInformation("Start handling ModuleIdentityRequest"); var iotHubConnectionString = System.Environment.GetEnvironmentVariable("iotHubConnectionString", EnvironmentVariableTarget.Process); RegistryManager registryManager = null; if (string.IsNullOrEmpty(iotHubConnectionString)) { log.LogWarning($"Missing IoT Hub Connection String!"); return; } if (eventGridEvent.EventType != "Microsoft.Devices.DeviceTelemetry") { log.LogWarning($"Wrong Event Grid EventType closing. {eventGridEvent.EventType}"); return; } //Extract Telemetry Payload //which operaton?? Create / Renew identiy? var eventDataString = eventGridEvent.Data.ToString(); log.LogDebug($"Deserializing event data {eventDataString}"); //Note: we assume a JSON payload in the body, 'UTF-8' Encoded AND 'application/json' content type. Otherwise body will be base64 encoded var deviceEvent = JsonConvert.DeserializeObject <AADModuleIdentityOperationMessage>(eventDataString); if (deviceEvent.Properties != null) { if (deviceEvent.Properties.TelemetryType != null) { if (deviceEvent.Properties.TelemetryType == "AADModuleIdentityOperation") { //ok this is an AAD Module Identity Operation telemetry message var deviceId = deviceEvent.SystemProperties.EdgeDeviceId; var moduleId = deviceEvent.SystemProperties.EdgeModuleId; //Get Module info from IoT Hub registryManager = RegistryManager.CreateFromConnectionString(iotHubConnectionString); var module = await registryManager.GetModuleAsync(deviceId as string, moduleId as string); if (module == null) { log.LogError("Error in event grid processing, module info could not be loaded. Exiting function."); return; } //download the Module Twin var deviceTwin = await registryManager.GetTwinAsync(deviceId, moduleId); //and parse into the object model var updateTwin = AADModuleTwin.Parse(deviceTwin.ToJson()); //get the opeation type from the body of the telemetry message var opType = deviceEvent.Body.OperationType; //Updating the Twin to indicate that the telemetry message was received and pending to be processes OperationStatusEnum updatedStatus = OperationStatusEnum.Invalid; if (opType == OperationTypeEnum.CreateIdentity) { updatedStatus = OperationStatusEnum.CreatingIdentity; } if (opType == OperationTypeEnum.RefreshIdentity) { updatedStatus = OperationStatusEnum.RefreshingIdentity; } updateTwin.Properties.Desired.AADIdentityStatus = updatedStatus; string newTwinJson = updateTwin.ToJson(); log.LogDebug($"Updating Module twin\n{newTwinJson}"); var updateResult = await registryManager.UpdateTwinAsync(deviceId, moduleId, newTwinJson, deviceTwin.ETag); log.LogInformation($"Module Twin updated to version: {updateResult.Version}"); //Generate the User Name and Password (from Module Identity information) //This process is documented as "STEP 5 / 6" var userName = AADClientHelper.BuildUserName(deviceId, moduleId); var moduleKey = Convert.FromBase64String(module.Authentication.SymmetricKey.PrimaryKey); var password = SecretHelper.ComputeSecretWithHMACSHA256(moduleKey, userName); //Create or Update the Identity in AAD //how to use this code //0 prerequisites : be admin of a B2C tenant (tenant name should be lower case) //1 register an app with secret on your B2C tenant //2 provide API permissions Directory.ReadWrite.All -> grant admin consent https://docs.microsoft.com/en-us/graph/api/user-post-users?view=graph-rest-1.0&tabs=http string tenantName = System.Environment.GetEnvironmentVariable("b2cTenantName", EnvironmentVariableTarget.Process); string function_B2C_ClientId = System.Environment.GetEnvironmentVariable("function_B2C_ClientId", EnvironmentVariableTarget.Process); string function_B2C_ClientSecret = System.Environment.GetEnvironmentVariable("function_B2C_ClientSecret", EnvironmentVariableTarget.Process); string function_B2C_TenantId = System.Environment.GetEnvironmentVariable("function_B2C_TenantId", EnvironmentVariableTarget.Process); //First you need to identify your function agasint Azure AAD B2c as an app IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder .Create(function_B2C_ClientId) // clientId .WithTenantId(function_B2C_TenantId) //tenantId .WithClientSecret(function_B2C_ClientSecret) //clientsecret .Build(); ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication); //this requires : Install-Package Microsoft.Graph.Auth -PreRelease //Then you use this identity to push a new user GraphServiceClient graphClient = new GraphServiceClient(authProvider); var user = new User { AccountEnabled = true, DisplayName = userName, UserPrincipalName = $"{userName}@{tenantName}.onmicrosoft.com", MailNickname = userName, PasswordProfile = new PasswordProfile { ForceChangePasswordNextSignIn = false, Password = password } }; try { log.LogInformation($"Adding User: {userName} to the {tenantName} B2C Tenant"); await graphClient.Users .Request() .AddAsync(user); //Update Module Twin to reflect the operation feedback updatedStatus = OperationStatusEnum.IdentityCreated; } catch (Exception ex) { log.LogError(ex, $"An Exception happened while adding User: {userName} to the {tenantName} B2C Tenant"); //Update Module Twin to reflect the operation feedback updatedStatus = OperationStatusEnum.FailedWhileCreatingIdentity; } updateTwin.Tags.AADIdentityPassword = password; updateTwin.Properties.Desired.AADIdentityStatus = updatedStatus; updateTwin.Properties.Desired.AADIdentityUserName = userName; newTwinJson = updateTwin.ToJson(); log.LogDebug($"Updating Module twin\n{newTwinJson}"); try { //reag again the twin to obtain the latest etag deviceTwin = await registryManager.GetTwinAsync(deviceId, moduleId); //Update Device Twin to notify that the user in the AAD is already created! updateResult = await registryManager.UpdateTwinAsync(deviceId, moduleId, newTwinJson, deviceTwin.ETag); } catch (Exception ex) { log.LogError(ex, $"Error while updating Module Twin"); } log.LogInformation($"Module Twin updated to version: {updateResult.Version}"); } } } }