Azure Active Directory and Microsoft Graph API: Solution to PageToken Expiration Issue When Enumerating Directory objects.

Hello,

Before starting this post, I want to make a special thanks to Stephane Eyskens who helped me to troubleshoot this particular issue.  

When trying to enumerate Directory object via the Microsoft Graph API, by default only 100 items are returned. You need then to use the SkipToken returned in the response to be able to continue to iterate and collect next objects. Basically the SkipToken, store the index of your iteration (“current page number”)

Example:

https://graph.windows.net/%5BMYTENANTGUID%5D/groups/%5BMYADGROUPGUID%5D/members?api-version=1.6&$skiptoken=%5BMYSKIPTOKEN%5D;

Continuing this operation until you arrive at the end of your enumeration and the SkipToken will be empty.

But, I have noticed that when you reach more than 4000 objects to retrieve, in the case you want to retrieve all users in your Azure Active Directory for example, you might then face an Azure transient issue with the message: “PageToken expired” and of course you can’t renew it, because this is not working like an AccessToken or RefreshToken.

Even by using the C# library or Web Request this error occurred…

After several investigations, we found that to solve this problem, we have to simply make a retry with the same SkipToken to be able to retrieve all information and continue your iteration as nothing happen 🙂

The code below provide you an example on how to retrieve all users of a specific group with the retry mechanism to avoid the PageToken issue.

This C# code use the Azure Active Directory library.

We have defined 5 Max retry and set the sleep between each retry to 5 sec to avoid to be throttled.


        ///<summary>
        /// Method to retrieve All Users from a Group in Active Direcry Using Microsoft Graph API
        /// </summary>

        /// <param name="activeDirectoryClient">the active directory client</param>
        /// <param name="groupName">a string, the name of the group to look into</param>
        private static void RetrieveAADUsers(ActiveDirectoryClient activeDirectoryClient, string groupName)
        {
            //get the group
            Microsoft.Azure.ActiveDirectory.GraphClient.Group retrievedGroup = activeDirectoryClient.Groups.Where(g => g.DisplayName == groupName).ExecuteAsync().Result.CurrentPage.FirstOrDefault() as Microsoft.Azure.ActiveDirectory.GraphClient.Group;
            if (retrievedGroup != null)
            {
                //retrieve members
                IGroupFetcher RetrievedGroupFetcher = retrievedGroup as IGroupFetcher;
                IPagedCollection<IDirectoryObject> members = RetrievedGroupFetcher.Members.ExecuteAsync().Result;
                //loop on all pages
                do
                {
                    List<IDirectoryObject> DirectoryObjects = members.CurrentPage.ToList();
                    //for loop on all members
                    foreach (IDirectoryObject DirectoryObject in DirectoryObjects)
                    {
                        //check if we have an user object
                        if (DirectoryObject is Microsoft.Azure.ActiveDirectory.GraphClient.User)
                        {
                            Microsoft.Azure.ActiveDirectory.GraphClient.User CurrentUser = DirectoryObject as Microsoft.Azure.ActiveDirectory.GraphClient.User;
                            var userEmail = CurrentUser.UserPrincipalName;
                            Console.WriteLine(userEmail);
                        }
                    }
                    //mechanism to avoid the PakeToken expiration issue
                    // when getting the next page
                    int maxRetry = 5;
                    int currentAttempt = 1;
                retry:
                    try
                    {
                        //try to get the next page and detect the PakeToken expiration exception
                        members = members.GetNextPageAsync().Result;
                    }
                    catch (System.AggregateException ex)
                    {
                        //check if max retry reached to get the next page
                        if (maxRetry == currentAttempt)
                        {
                            throw ex;
                        }
                        //wait 5sec before retrying, to get the next page
                        Thread.Sleep(5000);
                        currentAttempt++;
                        goto retry;
                    }
                } while (members != null);
            }
        }
Advertisements
This entry was posted in Azure, C# Solutions and tagged , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s