/// <summary> /// This method is called for Groups overage scenario. /// The method makes calls to Microsoft Graph to fetch the group membership of the authenticated user. /// </summary> /// <param name="graphClient"></param> /// <returns></returns> private static async Task <Dictionary <string, string> > ProcessUserGroupsB2C(GraphServiceClient graphClient, string userId) { Dictionary <string, string> groupClaims = new Dictionary <string, string>(); try { // Before instatntiating GraphServiceClient, the app should have granted admin consent for 'GroupMember.Read.All' permission. //var graphClient = context.HttpContext.RequestServices.GetService<GraphServiceClient>(); if (graphClient == null) { Console.WriteLine("No service for type 'Microsoft.Graph.GraphServiceClient' has been registered in the Startup."); } else { // The properties that we want to retrieve from MemberOf endpoint. string select = "id,displayName,onPremisesNetBiosName,onPremisesDomainName,onPremisesSamAccountNameonPremisesSecurityIdentifier"; IUserMemberOfCollectionWithReferencesPage memberPage = new UserMemberOfCollectionWithReferencesPage(); try { //Request to get groups and directory roles that the user is a direct member of. memberPage = await graphClient.Users[userId].MemberOf.Request().Select(select).GetAsync().ConfigureAwait(false); } catch (Exception graphEx) { var exMsg = graphEx.InnerException != null ? graphEx.InnerException.Message : graphEx.Message; Console.WriteLine("Call to Microsoft Graph failed: " + exMsg); } if (memberPage?.Count > 0) { // There is a limit to number of groups returned, below method make calls to Microsoft graph to get all the groups. var allgroups = ProcessIGraphServiceMemberOfCollectionPage(memberPage); if (allgroups?.Count > 0) { // Re-populate the `groups` claim with the complete list of groups fetched from MS Graph foreach (Group group in allgroups) { // The following code adds group ids to the 'groups' claim. But depending upon your reequirement and the format of the 'groups' claim selected in // the app registration, you might want to add other attributes than id to the `groups` claim, examples being; // For instance if the required format is 'NetBIOSDomain\sAMAccountName' then the code is as commented below: // groupClaims.Add(group.OnPremisesNetBiosName+"\\"+group.OnPremisesSamAccountName)); groupClaims.Add(group.Id, group.DisplayName); } } } } } catch (Exception ex) { Console.WriteLine(ex.Message); } return(groupClaims); }
/// <summary> /// This method inspects the claims collection created from the ID or Access token issued to a user and returns the groups that are present in the token . If it detects groups overage, /// the method then makes calls to Microsoft Graph to fetch the group membership of the authenticated user. /// </summary> /// <param name="context">TokenValidatedContext</param> private static async Task ProcessUserGroupsForOverage(TokenValidatedContext context) { try { // Before instatntiating GraphServiceClient, the app should have granted admin consent for 'GroupMember.Read.All' permission. var graphClient = context.HttpContext.RequestServices.GetService <GraphServiceClient>(); if (graphClient == null) { Console.WriteLine("No service for type 'Microsoft.Graph.GraphServiceClient' has been registered in the Startup."); } // Checks if the SecurityToken is not null. // For the Web App, SecurityToken contains value of the ID Token. else if (context.SecurityToken != null) { // Checks if 'JwtSecurityTokenUsedToCallWebAPI' key already exists. // This key is required to acquire Access Token for Graph Service Client. if (!context.HttpContext.Items.ContainsKey("JwtSecurityTokenUsedToCallWebAPI")) { // For Web App, access token is retrieved using account identifier. But at this point account identifier is null. // So, SecurityToken is saved in 'JwtSecurityTokenUsedToCallWebAPI' key. // The key is then used to get the Access Token on-behalf of user. context.HttpContext.Items.Add("JwtSecurityTokenUsedToCallWebAPI", context.SecurityToken as JwtSecurityToken); } // The properties that we want to retrieve from MemberOf endpoint. string select = "id,displayName,onPremisesNetBiosName,onPremisesDomainName,onPremisesSamAccountNameonPremisesSecurityIdentifier"; IUserMemberOfCollectionWithReferencesPage memberPage = new UserMemberOfCollectionWithReferencesPage(); try { //Request to get groups and directory roles that the user is a direct member of. memberPage = await graphClient.Me.MemberOf.Request().Select(select).GetAsync().ConfigureAwait(false); } catch (Exception graphEx) { var exMsg = graphEx.InnerException != null ? graphEx.InnerException.Message : graphEx.Message; Console.WriteLine("Call to Microsoft Graph failed: " + exMsg); } if (memberPage?.Count > 0) { // There is a limit to number of groups returned, below method make calls to Microsoft graph to get all the groups. var allgroups = ProcessIGraphServiceMemberOfCollectionPage(memberPage); if (allgroups?.Count > 0) { var identity = (ClaimsIdentity)context.Principal.Identity; if (identity != null) { // Checks if token is for protected APIs i.e., if token is 'Access Token'. if (IsAccessToken(identity)) { //Remove existing groups claims RemoveExistingGroupsClaims(identity); // Re-populate the `groups` claim with the complete list of groups fetched from MS Graph foreach (Group group in allgroups) { // The following code adds group ids to the 'groups' claim. But depending upon your reequirement and the format of the 'groups' claim selected in // the app registration, you might want to add other attributes than id to the `groups` claim, examples being; // For instance if the required format is 'NetBIOSDomain\sAMAccountName' then the code is as commented below: // identity.AddClaim(new Claim("groups", group.OnPremisesNetBiosName+"\\"+group.OnPremisesSamAccountName)); identity.AddClaim(new Claim("groups", group.Id)); } } } } } } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { // Checks if the key 'JwtSecurityTokenUsedToCallWebAPI' exists. if (context.HttpContext.Items.ContainsKey("JwtSecurityTokenUsedToCallWebAPI")) { // Removes 'JwtSecurityTokenUsedToCallWebAPI' from Items collection. // If not removed then it can cause failure to the application. // Because this key is also added by StoreTokenUsedToCallWebAPI method of Microsoft.Identity.Web. context.HttpContext.Items.Remove("JwtSecurityTokenUsedToCallWebAPI"); } } }