Created
June 13, 2025 05:12
-
-
Save rbrayb/a80ae95c37e63d00e2ec0ee9ed3db15e to your computer and use it in GitHub Desktop.
Provisioning user MFA programatically in Azure AD B2C
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Azure.Identity; | |
using Microsoft.Graph; | |
using Microsoft.Graph.Models; | |
using Microsoft.Graph.Models.ODataErrors; | |
using Microsoft.Identity.Client; | |
using System; | |
using System.Threading.Tasks; | |
namespace GraphPhoneAuthenticationDemo | |
{ | |
class Program | |
{ | |
// Asynchronous Main to run our sample. | |
static async Task Main(string[] args) | |
{ | |
// Usage: dotnet run [new|update|delete] <userId> [newPhoneNumber] | |
if (args.Length < 2 || (args[0] != "list" && args[0] != "new" && args[0] != "update" && args[0] != "delete")) | |
{ | |
Console.WriteLine("Usage: ManagePhoneNumber [list|new|update|delete] userObjectID [phoneNumber] [B|M]"); | |
Console.WriteLine(" where B=Business phone and M=Mobile phone"); | |
Console.WriteLine("\nExample: ManagePhoneNumber new 12345678-1234-1234-1234-123456789012 +645555551234 M"); | |
return; | |
} | |
// Replace with your Azure AD app registration details. | |
var clientId = "cd...05"; | |
var tenantId = "65...16"; | |
var clientSecret = "iK...wy"; | |
// Instantiate our helper with the required credentials. | |
var graphHelper = new GraphHelper(clientId, tenantId, clientSecret); | |
// Get userId and newPhoneNumber from command line | |
string userId = args[1]; | |
// Choose the type of phone number to update: | |
// For mobile phone: "3179e48a-750b-4051-897c-87b9720928f7" | |
// For business/office phone: "e37fc753-ff3b-4958-9484-eaa9425c82bc" | |
string phoneType = args[3]; | |
if (phoneType != "B" && phoneType != "M") | |
{ | |
Console.WriteLine("\nInvalid phone type. Use 'B' for business phone or 'M' for mobile phone."); | |
return; | |
} | |
string phoneMethodId = String.Empty; | |
if (phoneType == "B") | |
{ | |
// Business phone method ID | |
phoneMethodId = "e37fc753-ff3b-4958-9484-eaa9425c82bc"; | |
} | |
else if (phoneType == "M") | |
{ | |
// Mobile phone method ID | |
phoneMethodId = "3179e48a-750b-4051-897c-87b9720928f7"; | |
} | |
string newPhoneNumber = args.Length > 2 ? args[2] : null; | |
try | |
{ | |
if (args[0] == "list") | |
{ | |
if (string.IsNullOrEmpty(userId)) | |
{ | |
Console.WriteLine("For list, you must provide a user ID."); | |
return; | |
} | |
// List all phone authentication methods for the user. | |
await graphHelper.ListPhoneAuthenticationMethodsAsync(userId); | |
} | |
else if (args[0] == "update") | |
{ | |
if (string.IsNullOrEmpty(newPhoneNumber) || string.IsNullOrEmpty(userId)) | |
{ | |
Console.WriteLine("For update, you must provide a user ID and phone number."); | |
return; | |
} | |
await graphHelper.UpdatePhoneAuthenticationMethodAsync(userId, phoneMethodId, newPhoneNumber); | |
} | |
else if (args[0] == "new") | |
{ | |
if (string.IsNullOrEmpty(newPhoneNumber) || string.IsNullOrEmpty(userId)) | |
{ | |
Console.WriteLine("For new, you must provide a user ID and new phone number."); | |
return; | |
} | |
await graphHelper.AddPhoneAuthenticationMethodAsync(userId, newPhoneNumber); | |
} | |
else if (args[0] == "delete") | |
{ | |
if (string.IsNullOrEmpty(newPhoneNumber) || string.IsNullOrEmpty(userId)) | |
{ | |
Console.WriteLine("For delete, you must provide a user ID and phone number."); | |
return; | |
} | |
await graphHelper.DeletePhoneAuthenticationMethodAsync(userId, phoneMethodId); | |
} | |
} | |
catch (Exception ex) | |
{ | |
if (ex is ODataError odataError) | |
{ | |
Console.WriteLine("\nODataError Code: " + odataError.Error.Code); | |
Console.WriteLine("\nODataError Message: " + odataError.Error.Message); | |
} | |
else | |
{ | |
Console.WriteLine("\nException: " + ex.Message); | |
} | |
} | |
} | |
} | |
public class GraphHelper | |
{ | |
private readonly GraphServiceClient _graphClient; | |
public GraphHelper(string clientId, string tenantId, string clientSecret) | |
{ | |
// Use Azure.Identity's ClientSecretCredential for authentication. | |
var credential = new ClientSecretCredential(tenantId, clientId, clientSecret); | |
_graphClient = new GraphServiceClient(credential); | |
} | |
/// <summary> | |
/// Retrieves and displays all phone authentication methods for a given user. | |
/// </summary> | |
/// <param name="userId">The user's object id or UPN.</param> | |
public async Task ListPhoneAuthenticationMethodsAsync(string userId) | |
{ | |
var methods = await _graphClient.Users[userId] | |
.Authentication | |
.PhoneMethods | |
.GetAsync(); | |
Console.WriteLine("\nCurrent phone authentication methods:"); | |
bool foundPhoneNumber = false; | |
if (methods?.Value != null) | |
{ | |
foreach (var method in methods.Value) | |
{ | |
if (string.IsNullOrEmpty(method.PhoneNumber)) | |
{ | |
Console.WriteLine($"Method Id/Type: {method.Id}, Phone Number: Not set"); | |
continue; | |
} | |
else | |
{ | |
foundPhoneNumber = true; | |
Console.WriteLine($"Method Id/Type: {method.Id}, Phone Number: {method.PhoneNumber}"); | |
} | |
} | |
} | |
if (!foundPhoneNumber) | |
{ | |
Console.WriteLine("\nNo phone authentication methods with a phone number found for this user."); | |
} | |
} | |
/// <summary> | |
/// Updates a specific phone authentication method with a new phone number. | |
/// </summary> | |
/// <param name="userId">The user's object id or UPN.</param> | |
/// <param name="phoneMethodId"> | |
/// The identifier for the phone authentication method to update. | |
/// (e.g., mobile phone: "3179e48a-750b-4051-897c-87b9720928f7", business phone: "e37fc753-ff3b-4958-9484-eaa9425c82bc") | |
/// </param> | |
/// <param name="newPhoneNumber">The new phone number to set (properly formatted, e.g., "+1 5555551234").</param> | |
public async Task UpdatePhoneAuthenticationMethodAsync(string userId, string phoneMethodId, string newPhoneNumber) | |
{ | |
var updateData = new PhoneAuthenticationMethod | |
{ | |
PhoneNumber = newPhoneNumber | |
}; | |
try | |
{ | |
await _graphClient.Users[userId] | |
.Authentication | |
.PhoneMethods[phoneMethodId] | |
.PatchAsync(updateData); | |
Console.WriteLine("Phone number updated successfully."); | |
} | |
catch (Exception ex) | |
{ | |
if (ex is ODataError odataError) | |
{ | |
Console.WriteLine("\nODataError Code: " + odataError.Error.Code); | |
Console.WriteLine("\nODataError Message: " + odataError.Error.Message); | |
} | |
else | |
{ | |
Console.WriteLine("\nException: " + ex.Message); | |
} | |
} | |
} | |
/// <summary> | |
/// Adds a new phone authentication method for a given user. | |
/// </summary> | |
/// <param name="userId">The user's object id or UPN.</param> | |
/// <param name="newPhoneNumber">The new phone number to add (properly formatted, e.g., "+1 5555551234").</param> | |
public async Task AddPhoneAuthenticationMethodAsync(string userId, string newPhoneNumber) | |
{ | |
var newPhoneMethod = new PhoneAuthenticationMethod | |
{ | |
PhoneNumber = newPhoneNumber, | |
PhoneType = AuthenticationPhoneType.Mobile, // or .AlternateMobile, .Office | |
SmsSignInState = AuthenticationMethodSignInState.NotSupported // or Supported, if needed | |
}; | |
try | |
{ | |
await _graphClient.Users[userId] | |
.Authentication | |
.PhoneMethods | |
.PostAsync(newPhoneMethod); | |
Console.WriteLine("\nNew phone authentication method added successfully."); | |
} | |
catch (Exception ex) | |
{ | |
if (ex is ODataError odataError) | |
{ | |
Console.WriteLine("\nODataError Code: " + odataError.Error.Code); | |
Console.WriteLine("\nODataError Message: " + odataError.Error.Message); | |
} | |
else | |
{ | |
Console.WriteLine("\nException: " + ex.Message); | |
} | |
} | |
} | |
/// <summary> | |
/// Deletes a specific phone authentication method for a given user. | |
/// </summary> | |
/// <param name="userId">The user's object id or UPN.</param> | |
/// <param name="phoneMethodId">The identifier for the phone authentication method to delete.</param> | |
public async Task DeletePhoneAuthenticationMethodAsync(string userId, string phoneMethodId) | |
{ | |
try | |
{ | |
await _graphClient.Users[userId] | |
.Authentication | |
.PhoneMethods[phoneMethodId] | |
.DeleteAsync(); | |
Console.WriteLine("\nPhone authentication method deleted successfully."); | |
} | |
catch (Exception ex) | |
{ | |
if (ex is ODataError odataError) | |
{ | |
Console.WriteLine("\nODataError Code: " + odataError.Error.Code); | |
Console.WriteLine("\nODataError Message: " + odataError.Error.Message); | |
} | |
else | |
{ | |
Console.WriteLine("\nException: " + ex.Message); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://medium.com/the-new-control-plane/provisioning-user-mfa-programatically-in-azure-ad-b2c-6d77ef21dacf