Quote of the Day

more Quotes

Categories

Get notified of new posts

Buy me coffee

  • Home>
  • Azure>

Notes on using Microsoft Graph SDK to manage users in an Azure AD B2C tenant.

Published November 14, 2020 in Azure , Azure Active Directory , Azure ADB2C - 2 Comments

I recently worked on migration users’ accounts in an existing SQL database to azure AD B2C. I found some helpful articles from Microsoft that document different migration approaches and offer example codes on using Microsoft Graph SDK to manage the users. You can find the links to these articles and sample projects in the References section.

For the most part, I did not have much troubles with the basic CRUD operations. However, I had a bit of difficulties working with custom attributes and retrieving a user by email. In this post, I’m going to share some tips and caveats I learned. In particular, I’ll discuss:

  • The Microsoft Graph API permissions you need to manage the users.
  • Caveat on UserPrincipalName attribute and retrieving a user by email.
  • Setting and retrieving custom attributes in Azure ADB2C.

Microsoft Graph API permissions you may need

Per the document on Microsoft Graph permissions, you need at least the following application permissions to create and update users’ profiles:

Directory.ReadWrite.All

Per the document, the Directory.ReadWrite.All

Allows the app to read and write data in your organization’s directory, such as users, and groups, without a signed-in user. Does not allow user or group deletion.

Graph permission reference

However, as you read from the document, the above permissions are not sufficient if you also want to delete users’ accounts or reset users’ passwords. For those operations, you need the following permissions:

User.ReadWrite.All

Per the document, the User.ReadWrite.All

Allows the app to read and write the full set of profile properties, group membership, reports and managers of other users in your organization, without a signed-in user. Also allows the app to create and delete non-administrative users. Does not allow reset of user passwords.

Graph permission REFERENCES

Depending on your setup, choose between application or delegated permissions. In my app, I use the application permissions because the migration script does not involve a user. Fore more information, checkout this document.

Here is how you can add the permissions to your app via the azure portal:

  1. In the app registration page, go to API permissions.
  2. Click Add a permission.
  3. Under Select an API, select Microsoft Graph.
  4. Select Application permissions.
  5. Search for and add the permissions you want.
Add API permissions in azure AD B2C

Both the User.ReadWrite.All and the Directory.ReadWrite.All permissions require admin consent. If you are an admin of your tenant, once you have added the permissions, you can click on the Grant admin consent for {your tenant name} link under Configured permissions to grant the consent. If you are not an admin, reach out to someone with admin privileges, and that person can grant consent by going to the same screen under API permissions.

Grant admin consent

I have not tried to update a user’s password using the graph SDK. However, if you need to reset a user’s password, you’ll need to assign the User administrator role to your application. For more info, checkout the document.

Caveat on UserPrincipalName attribute

On retrieving a user object by id, I inspected the default properties to determine which properties I could use for filtering by email. Among the properties I got back, only “mail” and “userPrincipalName” attributes appeared appropriate. However, in our directory, all the users have blank mail. It should not be a problem as I expected to be able to use the userPrincipalName attribute because in the portal, the attribute appears to hold an email.

User principal name displays email in the azure adb2c portal

However, the value of userPrincipalName I got back from the graph API for that user was the object id of the user, not the email.

That was a bit surprising to me. Luckily, I’m used to surprises, having working with different APIs. I thought of using alternatives such as filtering on multiple attributes like givenName and surName. Although it’s unlikely we have two persons with the same given name and surname, it’s still possible, and I need an exact result, so filtering on those attributes would not work.

After consulting my best friend Google, I found the way to accomplish filtering by email is by using the issuerAssignedId attribute.

 private async Task<User> ExpectExactUserByEmail(string email)
        {
            // https://github.com/microsoftgraph/microsoft-graph-docs/issues/7282
            var filter = $"identities / any(id: id / issuer eq '{_migrationAppOptions.Issuer}' and id / issuerAssignedId eq '{email}')";
            return await ExpectExactUser(filter);
        }

private async Task<User> ExpectExactUser(string filter)
        {
            var users = await _graphClient.Users.Request().Filter(filter).Select(_defaultUserSelectAttributes).GetAsync();
            if (users.Count == 0)
            {
                throw new KeyNotFoundException($"Not able to find user with given filter: {filter}");
            }
            else if (users.Count > 1)
            {
                throw new ArgumentException($"More than one users found with given filter: {filter}");
            }
            return users[0];
        }

In the above snippets, notice the filtering is on identities and use both issuer and issuerAssignedId attributes, as per this issue. I suspect this only work because we use the “emailAddress” sign in type when creating a user. In fact, when creating a user using the SDK, I had to set the Identities property of the graph user object to the following:

Identities = new ObjectIdentity[] {
  new ObjectIdentity() {
    SignInType = "emailAddress",
    Issuer = _migrationAppOptions.Issuer,
    IssuerAssignedId = adB2CUser.UserEmail
  }
}

Settings and retrieving custom user attributes

I had seen dated posts that suggested the Microsoft Graph SDK did not support custom attributes in Azure AD B2C, and the alternative was to use Azure Active Directory Graph API. However, azure active directory graph api is fading away, and Microsoft Graph SDK is its successor.

At the time I used it, Microsoft Graph SDK does support working with custom attributes, as I was able to follow the example codes to set and retrieve custom attributes, so you can give it a try.

One thing to pay attention is you need to use the full name of the custom attribute, which you can construct using the following format:

‘extension_{client id of b2c-extensions app with all dash removed}_{name of attribute}’

The b2c-extensions-app is automatically registered when you create an azure ad b2c tenant.

b2c-extensions-app automatically registered

Below show the snippets I use to construct the full attribute name.

       private string ExtensionAttributeFullName(string attributeName)
        {
            return $"extension_{B2CExtensionsAppClientId.Replace("-", "")}_{attributeName}";
        }

When retrieving a user, you can use a $select clause to include the custom attribute in the result you get back from the graph, as the below example shows.

public async Task < User > GetUserByIdAsync(string userId) {
  var select = $ "id, givenName, surName, extension_2b76741733644c348db79bdc2e1002ce_MigrationSource";
  return await _graphClient.Users[userId].Request().Select(select).GetAsync();
}

When creating a user, you can set the custom attributes using a dictionary and assign to the AdditionalData property of the graph user, as shown in the below snippets.

public async Task < User > MergeUser([Required] ADB2CUserDTO adB2CUser) {
  IDictionary < string,
  object > extensionInstance = new Dictionary < string,
  object > {
    {
      _migrationAppOptions.ExtensionAttributeMigrationSourceFullName,
      "My web app"
    }
  };
  User user = new User() {
    AccountEnabled = true,
    DisplayName = adB2CUser.DisplayName,
    PasswordProfile = new PasswordProfile {
      ForceChangePasswordNextSignIn = true,
      Password = _passwordGenerator.GenerateAlphanumericPassword()
    },
    GivenName = adB2CUser.GivenName,
    Surname = adB2CUser.Surname,
    PostalCode = adB2CUser.PostalCode,
    State = adB2CUser.State,
    StreetAddress = adB2CUser.StreetAddress,
    City = adB2CUser.City,
    MobilePhone = adB2CUser.TelephoneNumber,
    Country = adB2CUser.Country,
    Identities = new ObjectIdentity[] {
      new ObjectIdentity() {
        SignInType = "emailAddress",
        Issuer = _migrationAppOptions.Issuer,
        IssuerAssignedId = adB2CUser.UserEmail
      }
    },
    AdditionalData = extensionInstance
  };
  return await _graphClient.Users.Request().AddAsync(user);
}

That’s about it for this post. As always, if you have questions, feel free to reach out.

References

Best practices for working with Microsoft Graph

Git repo of example codes for user migration

Azure ADB2C user attributes

Permissions for user delete and password update

Microsoft graph document issue: Not able to filter users by identity

Microsoft Graph nuget package

2 comments