I'm new to the Azure B2C world. I'm attempting to create a Custom User attribute to store data for our application. I've created it in the Azure portal and assigned it to my Signup/SignIn policy. However, I want to be able to update/read this value programtically. I've been going down the route of using Graph API and registering Extensions. So two questions:
1) Are extensions/custom attributes the same thing? 2) I've tried this code and the returned extensions are always empty:
public void RegisterExtension()
{
string myRegisteredAppObjectId = "<>";
string json = @"{
""name"": ""My Custom Attribute"",
""dataType"": ""String"",
""targetObjects"": [
""User""
]
}";
B2CGraphClient b2CGraphClient = new B2CGraphClient();
b2CGraphClient.RegisterExtension(myRegisteredAppObjectId, json);
var extensions = JsonConvert.DeserializeObject(b2CGraphClient.GetExtensions(myRegisteredAppObjectId).Result);
}
B2CGraphClient.cs
public class B2CGraphClient
{
private string clientId { get; set; }
private string clientSecret { get; set; }
private string tenant { get; set; }
private AuthenticationContext authContext;
private ClientCredential credential;
public B2CGraphClient(string clientId, string clientSecret, string tenant)
{
// The client_id, client_secret, and tenant are pulled in from the App.config file
this.clientId = clientId;
this.clientSecret = clientSecret;
this.tenant = tenant;
// The AuthenticationContext is ADAL's primary class, in which you indicate the direcotry to use.
this.authContext = new AuthenticationContext("https://login.microsoftonline.com/" + tenant);
// The ClientCredential is where you pass in your client_id and client_secret, which are
// provided to Azure AD in order to receive an access_token using the app's identity.
this.credential = new ClientCredential(clientId, clientSecret);
}
public async Task<string> DeleteUser(string objectId)
{
return await SendGraphDeleteRequest("/users/" + objectId);
}
public async Task<string> RegisterExtension(string objectId, string body)
{
return await SendGraphPostRequest("/applications/" + objectId + "/extensionProperties", body);
}
public async Task<string> GetExtensions(string appObjectId)
{
return await SendGraphGetRequest("/applications/" + appObjectId + "/extensionProperties", null);
}
private async Task<string> SendGraphPostRequest(string api, string json)
{
// NOTE: This client uses ADAL v2, not ADAL v4
AuthenticationResult result = authContext.AcquireToken(Globals.aadGraphResourceId, credential);
HttpClient http = new HttpClient();
string url = Globals.aadGraphEndpoint + tenant + api + "?" + Globals.aadGraphVersion;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("POST " + url);
Console.WriteLine("Authorization: Bearer " + result.AccessToken.Substring(0, 80) + "...");
Console.WriteLine("Content-Type: application/json");
Console.WriteLine("");
Console.WriteLine(json);
Console.WriteLine("");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
object formatted = JsonConvert.DeserializeObject(error);
throw new WebException("Error Calling the Graph API: \n" + JsonConvert.SerializeObject(formatted, Formatting.Indented));
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine((int)response.StatusCode + ": " + response.ReasonPhrase);
Console.WriteLine("");
return await response.Content.ReadAsStringAsync();
}
public async Task<string> SendGraphGetRequest(string api, string query)
{
// First, use ADAL to acquire a token using the app's identity (the credential)
// The first parameter is the resource we want an access_token for; in this case, the Graph API.
AuthenticationResult result = authContext.AcquireToken("https://graph.windows.net", credential);
// For B2C user managment, be sure to use the 1.6 Graph API version.
HttpClient http = new HttpClient();
string url = "https://graph.windows.net/" + tenant + api + "?" + Globals.aadGraphVersion;
if (!string.IsNullOrEmpty(query))
{
url += "&" + query;
}
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("GET " + url);
Console.WriteLine("Authorization: Bearer " + result.AccessToken.Substring(0, 80) + "...");
Console.WriteLine("");
// Append the access token for the Graph API to the Authorization header of the request, using the Bearer scheme.
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
object formatted = JsonConvert.DeserializeObject(error);
throw new WebException("Error Calling the Graph API: \n" + JsonConvert.SerializeObject(formatted, Formatting.Indented));
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine((int)response.StatusCode + ": " + response.ReasonPhrase);
Console.WriteLine("");
return await response.Content.ReadAsStringAsync();
}
}
Of course, myRegisteredAppObjectId
has a valid GUID in it.
Thanks
Based on my test, extensions is the same thing as custom attributes.
I add the custom propery
MyCustomAttribute
following this tutorial and use a custom attribute in my policy. You could refer to my test steps.I download the B2C-GraphAPI-DotNet project from Github. Using following code to the custom attribute
Then we could get the custom properties from the extension.
Then you can then treat that attribute the same way you treat any other property on a user object
Such as create a user:
Query a user
Update a user