Skip to content

Commit e923739

Browse files
committed
Updates as per suggestions
1 parent 50076f8 commit e923739

File tree

5 files changed

+51
-27
lines changed

5 files changed

+51
-27
lines changed

5-WebApp-AuthZ/5-2-Groups/AppCreationScripts/Cleanup.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ param(
77
[string] $azureEnvironmentName
88
)
99

10-
#Requires -Modules AzureAD
10+
#Requires -Modules AzureAD -RunAsAdministrator
1111

1212

1313
if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) {

5-WebApp-AuthZ/5-2-Groups/AppCreationScripts/Configure.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ param(
77
[string] $azureEnvironmentName
88
)
99

10-
#Requires -Modules AzureAD
10+
#Requires -Modules AzureAD -RunAsAdministrator
1111

1212
<#
1313
This script creates the Azure AD applications needed for this sample and updates the configuration files

5-WebApp-AuthZ/5-2-Groups/Infrastructure/Constants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ public static class Constants
44
{
55
public const string ScopeUserRead = "User.Read";
66

7+
public const string ScopeGroupMemberRead = "GroupMember.Read.All";
8+
79
public const string BearerAuthorizationScheme = "Bearer";
810
}
911
}

5-WebApp-AuthZ/5-2-Groups/Services/MicrosoftGraph-Rest/GraphHelper.cs

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

5-WebApp-AuthZ/5-2-Groups/Startup.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.Identity.Web;
1111
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
1212
using Microsoft.Identity.Web.UI;
13+
using WebApp_OpenIDConnect_DotNet.Infrastructure;
1314
using WebApp_OpenIDConnect_DotNet.Services.GroupProcessing;
1415

1516
namespace WebApp_OpenIDConnect_DotNet
@@ -26,6 +27,7 @@ public Startup(IConfiguration configuration)
2627
// This method gets called by the runtime. Use this method to add services to the container.
2728
public void ConfigureServices(IServiceCollection services)
2829
{
30+
var initialScopes = new string[] { Constants.ScopeUserRead, Constants.ScopeGroupMemberRead };
2931
services.Configure<CookiePolicyOptions>(options =>
3032
{
3133
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
@@ -45,10 +47,10 @@ public void ConfigureServices(IServiceCollection services)
4547
options.Events.OnTokenValidated = async context =>
4648
{
4749
//Calls method to process groups overage claim.
48-
await GraphHelper.ProcessGroupsClaimforAccessToken(context);
50+
await GraphHelper.ProcessClaimsForGroupsOverage(context);
4951
};
5052
}, options => { Configuration.Bind("AzureAd", options); })
51-
.EnableTokenAcquisitionToCallDownstreamApi(options => Configuration.Bind("AzureAd", options))
53+
.EnableTokenAcquisitionToCallDownstreamApi(options => Configuration.Bind("AzureAd", options), initialScopes)
5254
.AddMicrosoftGraph(Configuration.GetSection("GraphAPI"))
5355
.AddInMemoryTokenCaches();
5456

0 commit comments

Comments
 (0)