@@ -13,39 +13,56 @@ namespace WebApp_OpenIDConnect_DotNet.Services.GroupProcessing
1313 public class GraphHelper
1414 {
1515 /// <summary>
16- /// Adds groups claim for group overage
16+ /// This method inspects the claims collection created from the ID or Access token and detects groups overage. If Groups overage is detected, the method then makes calls to
17+ /// Microsoft Graph to fetch the group membership of the authenticated user.
1718 /// </summary>
1819 /// <param name="context">TokenValidatedContext</param>
19- public static async Task ProcessGroupsClaimforAccessToken ( TokenValidatedContext context )
20+ public static async Task ProcessClaimsForGroupsOverage ( TokenValidatedContext context )
2021 {
2122 try
2223 {
23- //Checks if the token contains 'Group Overage' Claim.
24+ // Checks if the token contains 'Group Overage' Claim.
2425 if ( context . Principal . Claims . Any ( x => x . Type == "hasgroups" || ( x . Type == "_claim_names" && x . Value == "{\" groups\" :\" src1\" }" ) ) )
2526 {
26- //This API should have permission set for Microsoft graph: 'GroupMember.Read.All'
27- var graph = context . HttpContext . RequestServices . GetService < GraphServiceClient > ( ) ;
27+ // Before instatntiating GraphServiceClient, the app should have granted admin consent for 'GroupMember.Read.All' permission.
28+ var graphClient = context . HttpContext . RequestServices . GetService < GraphServiceClient > ( ) ;
2829
29- if ( graph == null )
30+ if ( graphClient == null )
3031 {
3132 Console . WriteLine ( "No service for type 'Microsoft.Graph.GraphServiceClient' has been registered." ) ;
3233 }
34+
35+ // Checks if the SecurityToken is not null.
36+ // For the Web App, SecurityToken contains value of the ID Token.
3337 else if ( context . SecurityToken != null )
3438 {
39+ // Checks if 'JwtSecurityTokenUsedToCallWebAPI' key already exists.
40+ // This key is required to acquire Access Token for Graph Service Client.
3541 if ( ! context . HttpContext . Items . ContainsKey ( "JwtSecurityTokenUsedToCallWebAPI" ) )
3642 {
37- //Added current access token in below key to get Access Token on-behalf of user.
43+ // For Web App, access token is retrieved using account identifier. But at this point account identifier is null.
44+ // So, SecurityToken is saved in 'JwtSecurityTokenUsedToCallWebAPI' key.
45+ // The key is then used to get the Access Token on-behalf of user.
3846 context . HttpContext . Items . Add ( "JwtSecurityTokenUsedToCallWebAPI" , context . SecurityToken as JwtSecurityToken ) ;
3947 }
40- //Specify the property names in the 'select' variable to get values for the specified properties.
41- string select = "id,displayName,onPremisesNetBiosName,onPremisesDomainName,onPremisesSamAccountNameonPremisesSecurityIdentifier" ;
42-
43- //Request to get groups and directory roles that the user is a direct member of.
44- var memberPage = await graph . Me . MemberOf . Request ( ) . Select ( select ) . GetAsync ( ) . ConfigureAwait ( false ) ;
4548
49+ // The properties that we want to retrieve from MemberOf endpoint.
50+ string select = "id,displayName,onPremisesNetBiosName,onPremisesDomainName,onPremisesSamAccountNameonPremisesSecurityIdentifier" ;
51+
52+ IUserMemberOfCollectionWithReferencesPage memberPage = new UserMemberOfCollectionWithReferencesPage ( ) ;
53+ try
54+ {
55+ //Request to get groups and directory roles that the user is a direct member of.
56+ memberPage = await graphClient . Me . MemberOf . Request ( ) . Select ( select ) . GetAsync ( ) . ConfigureAwait ( false ) ;
57+ }
58+ catch ( Exception graphEx )
59+ {
60+ var exMsg = graphEx . InnerException != null ? graphEx . InnerException . Message : graphEx . Message ;
61+ Console . WriteLine ( "Call to Microsoft Graph failed: " + exMsg ) ;
62+ }
4663 if ( memberPage ? . Count > 0 )
4764 {
48- //There is a limit to number of groups returned, below method make calls to Microsoft graph to get all the groups.
65+ // There is a limit to number of groups returned, below method make calls to Microsoft graph to get all the groups.
4966 var allgroups = ProcessIGraphServiceMemberOfCollectionPage ( memberPage ) ;
5067
5168 if ( allgroups ? . Count > 0 )
@@ -54,17 +71,18 @@ public static async Task ProcessGroupsClaimforAccessToken(TokenValidatedContext
5471
5572 if ( identity != null )
5673 {
57- //Remove existing groups claims
58- RemoveExistingClaims ( context , identity ) ;
74+ // Remove existing groups claims
75+ RemoveExistingClaims ( identity ) ;
5976
6077 List < Claim > groupClaims = new List < Claim > ( ) ;
6178
6279 foreach ( Group group in allgroups )
6380 {
64- //Claim is added in list and it can be used by saving the groups in session or as per project implementation.
65- //Adds group id as 'groups' claim. But it can be changed as per requirment.
66- //For instance if the required format is 'NetBIOSDomain\sAMAccountName' then the code is as commented below:
67- //groupClaims.AddClaim(new Claim("groups", group.OnPremisesNetBiosName+"\\"+group.OnPremisesSamAccountName));
81+ // The following code adds group ids to the 'groups' claim. But depending upon your reequirement and the format of the 'groups' claim selected in
82+ // the app registration, you might want to add other attributes than id to the `groups` claim, examples being;
83+
84+ // For instance if the required format is 'NetBIOSDomain\sAMAccountName' then the code is as commented below:
85+ // groupClaims.AddClaim(new Claim("groups", group.OnPremisesNetBiosName+"\\"+group.OnPremisesSamAccountName));
6886 groupClaims . Add ( new Claim ( "groups" , group . Id ) ) ;
6987 }
7088 }
@@ -79,10 +97,12 @@ public static async Task ProcessGroupsClaimforAccessToken(TokenValidatedContext
7997 }
8098 finally
8199 {
100+ // Checks if the key 'JwtSecurityTokenUsedToCallWebAPI' exists.
82101 if ( context . HttpContext . Items . ContainsKey ( "JwtSecurityTokenUsedToCallWebAPI" ) )
83102 {
84- //Remove the key as Microsoft.Identity.Web library utilizes this key.
85- //If not removed then it can cause failure to the application.
103+ // Removes 'JwtSecurityTokenUsedToCallWebAPI' from Items collection.
104+ // If not removed then it can cause failure to the application.
105+ // Because this key is also added by StoreTokenUsedToCallWebAPI method of Microsoft.Identity.Web.
86106 context . HttpContext . Items . Remove ( "JwtSecurityTokenUsedToCallWebAPI" ) ;
87107 }
88108 }
@@ -93,10 +113,10 @@ public static async Task ProcessGroupsClaimforAccessToken(TokenValidatedContext
93113 /// </summary>
94114 /// <param name="context"></param>
95115 /// <param name="identity"></param>
96- private static void RemoveExistingClaims ( TokenValidatedContext context , ClaimsIdentity identity )
116+ private static void RemoveExistingClaims ( ClaimsIdentity identity )
97117 {
98118 //clear existing claim
99- List < Claim > existingGroupsClaims = context . Principal . Claims . Where ( x => x . Type == "groups" ) . ToList ( ) ;
119+ List < Claim > existingGroupsClaims = identity . Claims . Where ( x => x . Type == "groups" ) . ToList ( ) ;
100120 if ( existingGroupsClaims ? . Count > 0 )
101121 {
102122 foreach ( Claim groupsClaim in existingGroupsClaims )
0 commit comments