@@ -13,39 +13,56 @@ namespace WebApp_OpenIDConnect_DotNet.Services.GroupProcessing
13
13
public class GraphHelper
14
14
{
15
15
/// <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.
17
18
/// </summary>
18
19
/// <param name="context">TokenValidatedContext</param>
19
- public static async Task ProcessGroupsClaimforAccessToken ( TokenValidatedContext context )
20
+ public static async Task ProcessClaimsForGroupsOverage ( TokenValidatedContext context )
20
21
{
21
22
try
22
23
{
23
- //Checks if the token contains 'Group Overage' Claim.
24
+ // Checks if the token contains 'Group Overage' Claim.
24
25
if ( context . Principal . Claims . Any ( x => x . Type == "hasgroups" || ( x . Type == "_claim_names" && x . Value == "{\" groups\" :\" src1\" }" ) ) )
25
26
{
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 > ( ) ;
28
29
29
- if ( graph == null )
30
+ if ( graphClient == null )
30
31
{
31
32
Console . WriteLine ( "No service for type 'Microsoft.Graph.GraphServiceClient' has been registered." ) ;
32
33
}
34
+
35
+ // Checks if the SecurityToken is not null.
36
+ // For the Web App, SecurityToken contains value of the ID Token.
33
37
else if ( context . SecurityToken != null )
34
38
{
39
+ // Checks if 'JwtSecurityTokenUsedToCallWebAPI' key already exists.
40
+ // This key is required to acquire Access Token for Graph Service Client.
35
41
if ( ! context . HttpContext . Items . ContainsKey ( "JwtSecurityTokenUsedToCallWebAPI" ) )
36
42
{
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.
38
46
context . HttpContext . Items . Add ( "JwtSecurityTokenUsedToCallWebAPI" , context . SecurityToken as JwtSecurityToken ) ;
39
47
}
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 ) ;
45
48
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
+ }
46
63
if ( memberPage ? . Count > 0 )
47
64
{
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.
49
66
var allgroups = ProcessIGraphServiceMemberOfCollectionPage ( memberPage ) ;
50
67
51
68
if ( allgroups ? . Count > 0 )
@@ -54,17 +71,18 @@ public static async Task ProcessGroupsClaimforAccessToken(TokenValidatedContext
54
71
55
72
if ( identity != null )
56
73
{
57
- //Remove existing groups claims
58
- RemoveExistingClaims ( context , identity ) ;
74
+ // Remove existing groups claims
75
+ RemoveExistingClaims ( identity ) ;
59
76
60
77
List < Claim > groupClaims = new List < Claim > ( ) ;
61
78
62
79
foreach ( Group group in allgroups )
63
80
{
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));
68
86
groupClaims . Add ( new Claim ( "groups" , group . Id ) ) ;
69
87
}
70
88
}
@@ -79,10 +97,12 @@ public static async Task ProcessGroupsClaimforAccessToken(TokenValidatedContext
79
97
}
80
98
finally
81
99
{
100
+ // Checks if the key 'JwtSecurityTokenUsedToCallWebAPI' exists.
82
101
if ( context . HttpContext . Items . ContainsKey ( "JwtSecurityTokenUsedToCallWebAPI" ) )
83
102
{
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.
86
106
context . HttpContext . Items . Remove ( "JwtSecurityTokenUsedToCallWebAPI" ) ;
87
107
}
88
108
}
@@ -93,10 +113,10 @@ public static async Task ProcessGroupsClaimforAccessToken(TokenValidatedContext
93
113
/// </summary>
94
114
/// <param name="context"></param>
95
115
/// <param name="identity"></param>
96
- private static void RemoveExistingClaims ( TokenValidatedContext context , ClaimsIdentity identity )
116
+ private static void RemoveExistingClaims ( ClaimsIdentity identity )
97
117
{
98
118
//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 ( ) ;
100
120
if ( existingGroupsClaims ? . Count > 0 )
101
121
{
102
122
foreach ( Claim groupsClaim in existingGroupsClaims )
0 commit comments