Skip to content

Instantly share code, notes, and snippets.

@rbrayb
Created March 31, 2025 02:52
Show Gist options
  • Save rbrayb/adae315bc2465c04cfc014605e938239 to your computer and use it in GitHub Desktop.
Save rbrayb/adae315bc2465c04cfc014605e938239 to your computer and use it in GitHub Desktop.
Using Azure AD B2C custom policies to implement Profile Edit on Entra External ID with Native auth
if (method == "auth")
{
Console.WriteLine("\n" + "Authenticating user");
using (var httpClient = new HttpClient())
{
// Add Host header
httpClient.DefaultRequestHeaders.Host = "externaltenant.ciamlogin.com";
// Step 1: Initiate
var initiateUrl = "https://externaltenant.ciamlogin.com/externaltenant.onmicrosoft.com/oauth2/v2.0/initiate";
var initiateRequestBody = new Dictionary<string, string>
{
// TODO
{ "client_id", "your client ID" },
{ "challenge_type", "password redirect" },
{ "username", email }
};
var initiateContent = new FormUrlEncodedContent(initiateRequestBody);
Console.WriteLine("\n" + "Initiate URL: " + initiateUrl);
Console.WriteLine("\n" + "Initiate Request Body: " + JsonConvert.SerializeObject(initiateRequestBody));
var initiateResponse = await httpClient.PostAsync(initiateUrl, initiateContent);
var initiateResponseContent = await initiateResponse.Content.ReadAsStringAsync();
Console.WriteLine("\n" + "Initiate Response Status Code: " + initiateResponse.StatusCode);
Console.WriteLine("\n" + "Initiate Response Content: " + initiateResponseContent);
if (!initiateResponse.IsSuccessStatusCode)
{
return new ConflictObjectResult(new B2CResponseModel($"Failed to initiate authentication. Response: {initiateResponseContent}", HttpStatusCode.Conflict));
}
var initiateJson = JsonConvert.DeserializeObject<JObject>(initiateResponseContent);
var continuationToken = initiateJson["continuation_token"].ToString();
// Step 2: Challenge
var challengeUrl = "https://externaltenant.ciamlogin.com/externaltenant.onmicrosoft.com/oauth2/v2.0/challenge";
var challengeRequestBody = new Dictionary<string, string>
{
// TODO
{ "client_id", "your client ID" },
{ "challenge_type", "password redirect" },
{ "continuation_token", continuationToken }
};
var challengeContent = new FormUrlEncodedContent(challengeRequestBody);
var challengeResponse = await httpClient.PostAsync(challengeUrl, challengeContent);
if (!challengeResponse.IsSuccessStatusCode)
{
return new ConflictObjectResult(new B2CResponseModel($"Failed to complete challenge.", HttpStatusCode.Conflict));
}
var challengeResponseContent = await challengeResponse.Content.ReadAsStringAsync();
var challengeJson = JsonConvert.DeserializeObject<JObject>(challengeResponseContent);
continuationToken = challengeJson["continuation_token"].ToString();
// Step 3: Token
var tokenUrl = "https://externaltenant.ciamlogin.com/externaltenant.onmicrosoft.com/oauth2/v2.0/token";
var tokenRequestBody = new Dictionary<string, string>
{
// TODO
{ "client_id", "your client ID" },
{ "password", password },
{ "continuation_token", continuationToken },
{ "grant_type", "password" },
{ "scope", "openid offline_access" }
};
var tokenContent = new FormUrlEncodedContent(tokenRequestBody);
var tokenResponse = await httpClient.PostAsync(tokenUrl, tokenContent);
if (!tokenResponse.IsSuccessStatusCode)
{
return new ConflictObjectResult(new B2CResponseModel($"Failed to obtain token.", HttpStatusCode.Conflict));
}
// Read the response content
var responseContent = await tokenResponse.Content.ReadAsStringAsync();
var jsonObj = JsonConvert.DeserializeObject(responseContent);
Console.WriteLine("\n" + "jsonObject " + jsonObj.ToString());
return new OkObjectResult(jsonObj);
}
}