Skip to content

Commit 22481c1

Browse files
Tonnes of enhancements
1 parent 5ed6654 commit 22481c1

File tree

11 files changed

+176
-39
lines changed

11 files changed

+176
-39
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66

77
$ErrorActionPreference = "Stop"
88

9+
# ObjectId of the user to be assigned to these security groups. The ObjectId can be obtained via Graph Explorer or in the "Users" blade on the portal.
10+
$usersobjectId = "695a3e1d-2e9f-4d24-aa3c-ac795c16f25c"
11+
912
Get-AzureADUser -ObjectId $usersobjectId
1013

11-
# ObjectId of the user to be assigned to these security groups. The ObjectId can be obtained via Graph Explorer or in the "Users" blade on the portal.
12-
$usersobjectId = "5b6e08a5-7789-4ae0-a4cb-3d73b4097752"
1314
$groupNamePrefix = "TestGroup"
1415
$numberOfGroupsToCreate = 222;
1516

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ $numberOfGroupsToDelete = 222;
1010
for($i = 1; $i -le $numberOfGroupsToDelete; $i++)
1111
{
1212
$groupName = $groupNamePrefix + $i
13-
$group = Get-AzureADGroup -SearchString $groupName
14-
Remove-AzureADGroup -ObjectId $group.ObjectId
15-
Write-Host "Successfully deleted $($group.DisplayName)"
13+
$groups = Get-AzureADGroup -SearchString $groupName
14+
15+
Foreach ($group in $groups)
16+
{
17+
Write-Host "Trying to delete group $($group.DisplayName)"
18+
Remove-AzureADGroup -ObjectId $group.ObjectId
19+
Write-Host "Successfully deleted $($group.DisplayName)"
20+
}
1621
}

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,42 @@ Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requ
102102
}
103103

104104

105+
Function UpdateLine([string] $line, [string] $value)
106+
{
107+
$index = $line.IndexOf('=')
108+
$delimiter = ';'
109+
if ($index -eq -1)
110+
{
111+
$index = $line.IndexOf(':')
112+
$delimiter = ','
113+
}
114+
if ($index -ige 0)
115+
{
116+
$line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter
117+
}
118+
return $line
119+
}
120+
121+
Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary)
122+
{
123+
$lines = Get-Content $configFilePath
124+
$index = 0
125+
while($index -lt $lines.Length)
126+
{
127+
$line = $lines[$index]
128+
foreach($key in $dictionary.Keys)
129+
{
130+
if ($line.Contains($key))
131+
{
132+
$lines[$index] = UpdateLine $line $dictionary[$key]
133+
}
134+
}
135+
$index++
136+
}
137+
138+
Set-Content -Path $configFilePath -Value $lines -Force
139+
}
140+
105141
Set-Content -Value "<html><body><table>" -Path createdApps.html
106142
Add-Content -Value "<thead><tr><th>Application</th><th>AppId</th><th>Url in the Azure portal</th></tr></thead><tbody>" -Path createdApps.html
107143

@@ -207,8 +243,10 @@ Function ConfigureApplications
207243

208244
# Update config file for 'webApp'
209245
$configFile = $pwd.Path + "\..\appsettings.json"
246+
210247
Write-Host "Updating the sample code ($configFile)"
211-
248+
$dictionary = @{ "ClientId" = $webAppAadApplication.AppId;"TenantId" = $tenantId;"Domain" = $tenantName;"ClientSecret" = $webAppAppKey };
249+
UpdateTextFile -configFilePath $configFile -dictionary $dictionary
212250
Write-Host ""
213251
Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------"
214252
Write-Host "IMPORTANT: Please follow the instructions below to complete a few manual step(s) in the Azure portal":

5-WebApp-AuthZ/5-2-Groups/AppCreationScripts/sample.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"CodeConfiguration": [
4646
{
4747
"App": "webApp",
48-
"SettingKind": "JSon",
48+
"SettingKind": "JSON",
4949
"SettingFile": "\\..\\appsettings.json",
5050
"Mappings": [
5151
{

5-WebApp-AuthZ/5-2-Groups/Controllers/HomeController.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
using Microsoft.AspNetCore.Authorization;
22
using Microsoft.AspNetCore.Mvc;
3+
using System.Collections.Generic;
34
using System.Diagnostics;
45
using System.Security.Claims;
56
using WebApp_OpenIDConnect_DotNet.Models;
7+
using WebApp_OpenIDConnect_DotNet.Infrastructure;
8+
using System.Linq;
9+
using Microsoft.AspNetCore.Http;
610

711
namespace WebApp_OpenIDConnect_DotNet.Controllers
812
{
@@ -16,14 +20,20 @@ public HomeController()
1620
public IActionResult Index()
1721
{
1822
ViewData["User"] = HttpContext.User;
23+
24+
// If groups overage occurred..
25+
if (HttpContext.Session.Keys.Contains("groupClaims"))
26+
{
27+
ViewData.Add("groupClaims", HttpContext.Session.GetAsByteArray("groupClaims") as List<string>);
28+
}
1929
return View();
2030
}
2131

2232
[AllowAnonymous]
2333
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
2434
public IActionResult Error()
2535
{
26-
return View(new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier});
36+
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
2737
}
2838
}
2939
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Newtonsoft.Json;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Runtime.Serialization.Formatters.Binary;
8+
using System.Text.Json;
9+
using System.Threading.Tasks;
10+
11+
namespace WebApp_OpenIDConnect_DotNet.Infrastructure
12+
{
13+
public static class SessionExtensions
14+
{
15+
public static void SetAsByteArray(this ISession session, string key, object toSerialize)
16+
{
17+
var binaryFormatter = new BinaryFormatter();
18+
var memoryStream = new MemoryStream();
19+
binaryFormatter.Serialize(memoryStream, toSerialize);
20+
21+
session.Set(key, memoryStream.ToArray());
22+
}
23+
24+
public static object GetAsByteArray(this ISession session, string key)
25+
{
26+
var memoryStream = new MemoryStream();
27+
var binaryFormatter = new BinaryFormatter();
28+
29+
var objectBytes = session.Get(key) as byte[];
30+
memoryStream.Write(objectBytes, 0, objectBytes.Length);
31+
memoryStream.Position = 0;
32+
33+
return binaryFormatter.Deserialize(memoryStream);
34+
35+
}
36+
}
37+
}

5-WebApp-AuthZ/5-2-Groups/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ As a first step you'll need to:
135135
- In the *Commonly used Microsoft APIs* section, click on **Microsoft Graph**
136136
- In the **Delegated permissions** section, select the **GroupMember.Read.All** in the list. Use the search box if necessary.
137137
- Click on the **Add permissions** button at the bottom.
138+
1. At this stage permissions are assigned correctly and the **GroupMember.Read.All** requires admin to consent.
139+
Click the **Grant/revoke admin consent for {tenant}** button, and then select **Yes** when you are asked if you want to grant consent for the
140+
requested permissions for all account in the tenant.
141+
You need to be an Azure AD tenant admin to do this.
138142

139143
#### Configure your application to receive the **groups** claim
140144

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

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@
77
using System.Linq;
88
using System.Security.Claims;
99
using System.Threading.Tasks;
10+
using WebApp_OpenIDConnect_DotNet.Infrastructure;
1011

11-
namespace WebApp_OpenIDConnect_DotNet.Services.GroupProcessing
12+
namespace WebApp_OpenIDConnect_DotNet.Services
1213
{
1314
public class GraphHelper
1415
{
1516
/// <summary>
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+
/// 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,
18+
/// the method then makes calls to Microsoft Graph to fetch the group membership of the authenticated user.
1819
/// </summary>
1920
/// <param name="context">TokenValidatedContext</param>
20-
public static async Task ProcessClaimsForGroupsOverage(TokenValidatedContext context)
21+
public static async Task<List<string>> GetSignedInUsersGroups(TokenValidatedContext context)
2122
{
23+
List<string> groupClaims = new List<string>();
24+
2225
try
2326
{
2427
// Checks if the incoming token contained a 'Group Overage' claim.
@@ -36,7 +39,7 @@ public static async Task ProcessClaimsForGroupsOverage(TokenValidatedContext con
3639
// For the Web App, SecurityToken contains value of the ID Token.
3740
else if (context.SecurityToken != null)
3841
{
39-
// Checks if 'JwtSecurityTokenUsedToCallWebAPI' key already exists.
42+
// Checks if 'JwtSecurityTokenUsedToCallWebAPI' key already exists.
4043
// This key is required to acquire Access Token for Graph Service Client.
4144
if (!context.HttpContext.Items.ContainsKey("JwtSecurityTokenUsedToCallWebAPI"))
4245
{
@@ -48,18 +51,19 @@ public static async Task ProcessClaimsForGroupsOverage(TokenValidatedContext con
4851

4952
// The properties that we want to retrieve from MemberOf endpoint.
5053
string select = "id,displayName,onPremisesNetBiosName,onPremisesDomainName,onPremisesSamAccountNameonPremisesSecurityIdentifier";
51-
54+
5255
IUserMemberOfCollectionWithReferencesPage memberPage = new UserMemberOfCollectionWithReferencesPage();
5356
try
5457
{
5558
//Request to get groups and directory roles that the user is a direct member of.
5659
memberPage = await graphClient.Me.MemberOf.Request().Select(select).GetAsync().ConfigureAwait(false);
5760
}
58-
catch(Exception graphEx)
61+
catch (Exception graphEx)
5962
{
6063
var exMsg = graphEx.InnerException != null ? graphEx.InnerException.Message : graphEx.Message;
61-
Console.WriteLine("Call to Microsoft Graph failed: "+ exMsg);
64+
Console.WriteLine("Call to Microsoft Graph failed: " + exMsg);
6265
}
66+
6367
if (memberPage?.Count > 0)
6468
{
6569
// There is a limit to number of groups returned, below method make calls to Microsoft graph to get all the groups.
@@ -72,9 +76,7 @@ public static async Task ProcessClaimsForGroupsOverage(TokenValidatedContext con
7276
if (identity != null)
7377
{
7478
// Remove any existing groups claim
75-
RemoveExistingClaim(identity);
76-
77-
List<Claim> groupClaims = new List<Claim>();
79+
RemoveExistingClaims(identity);
7880

7981
// Re-populate the `groups` claim with the complete list of groups fetched from MS Graph
8082
foreach (Group group in allgroups)
@@ -83,8 +85,8 @@ public static async Task ProcessClaimsForGroupsOverage(TokenValidatedContext con
8385
// the app registration, you might want to add other attributes than id to the `groups` claim, examples being;
8486

8587
// For instance if the required format is 'NetBIOSDomain\sAMAccountName' then the code is as commented below:
86-
// groupClaims.AddClaim(new Claim("groups", group.OnPremisesNetBiosName+"\\"+group.OnPremisesSamAccountName));
87-
groupClaims.Add(new Claim("groups", group.Id));
88+
// groupClaims.Add(group.OnPremisesNetBiosName+"\\"+group.OnPremisesSamAccountName));
89+
groupClaims.Add(group.Id);
8890
}
8991
}
9092
}
@@ -103,10 +105,15 @@ public static async Task ProcessClaimsForGroupsOverage(TokenValidatedContext con
103105
{
104106
// Removes 'JwtSecurityTokenUsedToCallWebAPI' from Items collection.
105107
// If not removed then it can cause failure to the application.
106-
// Because this key is also added by StoreTokenUsedToCallWebAPI method of Microsoft.Identity.Web.
108+
// Because this key is also added by StoreTokenUsedToCallWebAPI method of Microsoft.Identity.Web.
107109
context.HttpContext.Items.Remove("JwtSecurityTokenUsedToCallWebAPI");
108110
}
109111
}
112+
113+
// Here we add the groups in a session variable to print on the home page for informational purposes
114+
context.HttpContext.Session.SetAsByteArray("groupClaims", groupClaims);
115+
116+
return groupClaims;
110117
}
111118

112119
/// <summary>

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
using Microsoft.Extensions.DependencyInjection;
99
using Microsoft.Extensions.Hosting;
1010
using Microsoft.Identity.Web;
11-
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
1211
using Microsoft.Identity.Web.UI;
12+
using System;
1313
using WebApp_OpenIDConnect_DotNet.Infrastructure;
14-
using WebApp_OpenIDConnect_DotNet.Services.GroupProcessing;
14+
using WebApp_OpenIDConnect_DotNet.Services;
1515

1616
namespace WebApp_OpenIDConnect_DotNet
1717
{
@@ -33,7 +33,7 @@ public void ConfigureServices(IServiceCollection services)
3333
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
3434
options.CheckConsentNeeded = context => true;
3535
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
36-
// Handling SameSite cookie according to https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1
36+
// Handling SameSite cookie according to https://docs.microsoft.com/en-us/aspnet/core/security/samesite
3737
options.HandleSameSiteCookieCompatibility();
3838
});
3939

@@ -47,7 +47,7 @@ public void ConfigureServices(IServiceCollection services)
4747
options.Events.OnTokenValidated = async context =>
4848
{
4949
//Calls method to process groups overage claim.
50-
await GraphHelper.ProcessClaimsForGroupsOverage(context);
50+
await GraphHelper.GetSignedInUsersGroups(context);
5151
};
5252
}, options => { Configuration.Bind("AzureAd", options); })
5353
.EnableTokenAcquisitionToCallDownstreamApi(options => Configuration.Bind("AzureAd", options), initialScopes)
@@ -62,6 +62,14 @@ public void ConfigureServices(IServiceCollection services)
6262
// options.TokenValidationParameters.RoleClaimType = "groups";
6363
});
6464

65+
services.AddDistributedMemoryCache();
66+
services.AddSession(options =>
67+
{
68+
options.IdleTimeout = TimeSpan.FromMinutes(1);
69+
options.Cookie.HttpOnly = true;
70+
options.Cookie.IsEssential = true;
71+
});
72+
6573
services.AddControllersWithViews(options =>
6674
{
6775
var policy = new AuthorizationPolicyBuilder()
@@ -88,12 +96,13 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
8896

8997
app.UseHttpsRedirection();
9098
app.UseStaticFiles();
91-
app.UseCookiePolicy();
9299

93100
app.UseRouting();
101+
app.UseSession();
94102
app.UseAuthentication();
95103
app.UseAuthorization();
96104

105+
97106
app.UseEndpoints(endpoints =>
98107
{
99108
endpoints.MapControllerRoute(

5-WebApp-AuthZ/5-2-Groups/Views/Home/Index.cshtml

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,44 @@
2626

2727
@foreach (var claim in user.Claims)
2828
{
29-
<tr>
30-
@{
31-
if (claim.Type == "groups")
32-
{
33-
<td><b>@claim.Type</b></td>
34-
}
35-
else
36-
{
37-
<td>@claim.Type</td>
38-
}
29+
<tr>
30+
@{
31+
if (claim.Type == "groups")
32+
{
33+
<td><b>@claim.Type</b></td>
34+
}
35+
else
36+
{
37+
<td>@claim.Type</td>
38+
}
39+
}
40+
41+
<td>@claim.Value</td>
42+
</tr>
43+
}
44+
45+
</table>
46+
47+
@{
48+
List<string> groupClaims = new List<string>();
49+
50+
if (ViewData.ContainsKey("groupClaims"))
51+
{
52+
groupClaims = ViewData["groupClaims"] as List<string>;
3953
}
4054

41-
<td>@claim.Value</td>
42-
</tr>
55+
}
56+
57+
<table class="table table-striped table-bordered table-condensed table-hover">
58+
<tr>
59+
<td colspan="2">If groups overage occured, the groups fetched from Graph will be listed below</td>
60+
</tr>
61+
62+
@foreach (var group in groupClaims)
63+
{
64+
<tr>
65+
<td colspan="2">@group</td>
66+
</tr>
4367
}
4468

4569
</table>

0 commit comments

Comments
 (0)