Skip to content

Commit 6527273

Browse files
minor edits
1 parent b469c65 commit 6527273

File tree

6 files changed

+80
-89
lines changed

6 files changed

+80
-89
lines changed

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

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,47 @@
1-
# Registering the sample apps with Microsoft identity platform and updating the configuration files using PowerShell scripts
1+
# Registering the sample apps with the Microsoft identity platform and updating the configuration files using PowerShell
22

33
## Overview
44

55
### Quick summary
66

77
1. On Windows run PowerShell and navigate to the root of the cloned directory
88
1. In PowerShell run:
9+
910
```PowerShell
1011
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
1112
```
13+
1214
1. Run the script to create your Azure AD application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below)
15+
1316
```PowerShell
1417
cd .\AppCreationScripts\
1518
.\Configure.ps1
1619
```
20+
1721
1. Open the Visual Studio solution and click start
1822

1923
### More details
2024

2125
The following paragraphs:
2226

23-
- [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios.
24-
- Explain the [pre-requisites](#pre-requisites)
25-
- Explain [four ways of running the scripts](#four-ways-to-run-the-script):
26-
- [Interactively](#option-1-interactive) to create the app in your home tenant
27-
- [Passing credentials](#option-2-non-interactive) to create the app in your home tenant
28-
- [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant)
29-
- [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant)
30-
- [Passing environment name, for Sovereign clouds](#running-the-script-on-azure-sovereign-clouds)
31-
32-
## Goal of the scripts
27+
- [Registering the sample apps with the Microsoft identity platform and updating the configuration files using PowerShell](#Registering-the-sample-apps-with-the-Microsoft-identity-platform-and-updating-the-configuration-files-using-PowerShell)
28+
- [Overview](#Overview)
29+
- [Quick summary](#Quick-summary)
30+
- [More details](#More-details)
31+
- [Goal of the provided scripts](#Goal-of-the-provided-scripts)
32+
- [Presentation of the scripts](#Presentation-of-the-scripts)
33+
- [Usage pattern for tests and DevOps scenarios](#Usage-pattern-for-tests-and-DevOps-scenarios)
34+
- [How to use the app creation scripts?](#How-to-use-the-app-creation-scripts)
35+
- [Pre-requisites](#Pre-requisites)
36+
- [Run the script and start running](#Run-the-script-and-start-running)
37+
- [Four ways to run the script](#Four-ways-to-run-the-script)
38+
- [Option 1 (interactive)](#Option-1-interactive)
39+
- [Option 2 (non-interactive)](#Option-2-non-interactive)
40+
- [Option 3 (Interactive, but create apps in a specified tenant)](#Option-3-Interactive-but-create-apps-in-a-specified-tenant)
41+
- [Option 4 (non-interactive, and create apps in a specified tenant)](#Option-4-non-interactive-and-create-apps-in-a-specified-tenant)
42+
- [Running the script on Azure Sovereign clouds](#Running-the-script-on-Azure-Sovereign-clouds)
43+
44+
## Goal of the provided scripts
3345

3446
### Presentation of the scripts
3547

@@ -56,36 +68,43 @@ The `Configure.ps1` will stop if it tries to create an Azure AD application whic
5668
### Pre-requisites
5769

5870
1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window)
59-
2. Navigate to the root directory of the project.
60-
3. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command:
71+
1. Navigate to the root directory of the project.
72+
1. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command:
73+
6174
```PowerShell
6275
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
6376
```
64-
### (Optionally) install AzureAD PowerShell modules
77+
78+
1. ### (Optionally) install AzureAD PowerShell modules
79+
2.
6580
The scripts install the required PowerShell module (AzureAD) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps:
6681
67-
4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this:
82+
1. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this:
6883
6984
1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator).
7085
2. Type:
86+
7187
```PowerShell
7288
Install-Module AzureAD
7389
```
7490
7591
or if you cannot be administrator on your machine, run:
92+
7693
```PowerShell
7794
Install-Module AzureAD -Scope CurrentUser
7895
```
7996
8097
### Run the script and start running
8198
82-
5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo,
99+
1. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo,
100+
83101
```PowerShell
84102
cd AppCreationScripts
85103
```
86-
6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that.
87-
7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**.
88-
8. select **Start** for the projects
104+
105+
1. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that.
106+
1. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**.
107+
1. select **Start** for the projects
89108
90109
You're done. this just works!
91110
@@ -123,6 +142,7 @@ Of course, in real life, you might already get the password as a `SecureString`.
123142
#### Option 3 (Interactive, but create apps in a specified tenant)
124143

125144
if you want to create the apps in a particular tenant, you can use the following option:
145+
126146
- open the [Azure portal](https://portal.azure.com)
127147
- Select the Azure Active directory you are interested in (in the combo-box below your name on the top right of the browser window)
128148
- Find the "Active Directory" object in this tenant

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: 2 additions & 40 deletions
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
@@ -102,42 +102,6 @@ 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-
141105
Set-Content -Value "<html><body><table>" -Path createdApps.html
142106
Add-Content -Value "<thead><tr><th>Application</th><th>AppId</th><th>Url in the Azure portal</th></tr></thead><tbody>" -Path createdApps.html
143107

@@ -233,7 +197,7 @@ Function ConfigureApplications
233197
# Add Required Resources Access (from 'webApp' to 'Microsoft Graph')
234198
Write-Host "Getting access from 'webApp' to 'Microsoft Graph'"
235199
$requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" `
236-
-requiredDelegatedPermissions "GroupMember.Read.All" `
200+
-requiredDelegatedPermissions "User.Read GroupMember.Read.All" `
237201

238202
$requiredResourcesAccess.Add($requiredPermissions)
239203

@@ -244,8 +208,6 @@ Function ConfigureApplications
244208
# Update config file for 'webApp'
245209
$configFile = $pwd.Path + "\..\appsettings.json"
246210
Write-Host "Updating the sample code ($configFile)"
247-
$dictionary = @{ "ClientId" = $webAppAadApplication.AppId;"TenantId" = $tenantId;"Domain" = $tenantName;"ClientSecret" = $webAppAppKey };
248-
UpdateTextFile -configFilePath $configFile -dictionary $dictionary
249211

250212
Add-Content -Value "</tbody></table></body></html>" -Path createdApps.html
251213
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"RequiredResourcesAccess": [
2626
{
2727
"Resource": "Microsoft Graph",
28-
"DelegatedPermissions": [ "GroupMember.Read.All" ]
28+
"DelegatedPermissions": [ "User.Read GroupMember.Read.All" ]
2929
}
3030
]
3131
}

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

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,55 @@
1-
using System.Threading.Tasks;
1+
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
2+
using Microsoft.Extensions.DependencyInjection;
23
using Microsoft.Graph;
34
using System;
45
using System.Collections.Generic;
5-
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
6-
using Microsoft.Extensions.DependencyInjection;
76
using System.IdentityModel.Tokens.Jwt;
8-
using System.Security.Claims;
97
using System.Linq;
8+
using System.Security.Claims;
9+
using System.Threading.Tasks;
1010

1111
namespace WebApp_OpenIDConnect_DotNet.Services.GroupProcessing
1212
{
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 incoming token contained a '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+
// For this API call to succeed, the app should have permission 'GroupMember.Read.All' granted.
2728
var graph = context.HttpContext.RequestServices.GetService<GraphServiceClient>();
2829

2930
if (graph == null)
3031
{
31-
Console.WriteLine("No service for type 'Microsoft.Graph.GraphServiceClient' has been registered.");
32+
Console.WriteLine("No service for type 'Microsoft.Graph.GraphServiceClient' has been registered in the Startup.");
3233
}
3334
else if (context.SecurityToken != null)
3435
{
36+
// Check if an on-behalf-of all was made to a Web API
3537
if (!context.HttpContext.Items.ContainsKey("JwtSecurityTokenUsedToCallWebAPI"))
3638
{
37-
//Added current access token in below key to get Access Token on-behalf of user.
39+
// extract the cached AT that was presented to the Web API
3840
context.HttpContext.Items.Add("JwtSecurityTokenUsedToCallWebAPI", context.SecurityToken as JwtSecurityToken);
3941
}
40-
//Specify the property names in the 'select' variable to get values for the specified properties.
42+
43+
// We do not want to pull all attributes of a group from MS Graph, so we use a 'select' to just pick the ones we need.
4144
string select = "id,displayName,onPremisesNetBiosName,onPremisesDomainName,onPremisesSamAccountNameonPremisesSecurityIdentifier";
4245

43-
//Request to get groups and directory roles that the user is a direct member of.
46+
// TODO: this line needs a try-catch, with the exception error message being "A call to Microsoft Graph failed, the error is <whatever>"
47+
// Make a Graph call to get groups and directory roles that the user is a direct member of.
4448
var memberPage = await graph.Me.MemberOf.Request().Select(select).GetAsync().ConfigureAwait(false);
4549

4650
if (memberPage?.Count > 0)
4751
{
48-
//There is a limit to number of groups returned, below method make calls to Microsoft graph to get all the groups.
52+
// If the result is paginated, this method will process all the pages for us.
4953
var allgroups = ProcessIGraphServiceMemberOfCollectionPage(memberPage);
5054

5155
if (allgroups?.Count > 0)
@@ -54,17 +58,19 @@ public static async Task ProcessGroupsClaimforAccessToken(TokenValidatedContext
5458

5559
if (identity != null)
5660
{
57-
//Remove existing groups claims
58-
RemoveExistingClaims(context, identity);
61+
// Remove any existing groups claim
62+
RemoveExistingClaim(identity);
5963

6064
List<Claim> groupClaims = new List<Claim>();
6165

66+
// Re-populate the `groups` claim with the complete list of groups fetched from MS Graph
6267
foreach (Group group in allgroups)
6368
{
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));
69+
// The following code adds group ids to the 'groups' claim. But depending upon your reequirement and the format of the 'groups' claim selected in
70+
// the app registration, you might want to add other attributes than id to the `groups` claim, examples being;
71+
72+
// For instance if the required format is 'NetBIOSDomain\sAMAccountName' then the code is as commented below:
73+
// groupClaims.AddClaim(new Claim("groups", group.OnPremisesNetBiosName+"\\"+group.OnPremisesSamAccountName));
6874
groupClaims.Add(new Claim("groups", group.Id));
6975
}
7076
}
@@ -81,8 +87,9 @@ public static async Task ProcessGroupsClaimforAccessToken(TokenValidatedContext
8187
{
8288
if (context.HttpContext.Items.ContainsKey("JwtSecurityTokenUsedToCallWebAPI"))
8389
{
84-
//Remove the key as Microsoft.Identity.Web library utilizes this key.
85-
//If not removed then it can cause failure to the application.
90+
// TODO: The following comment makes no sense !
91+
// Remove the key as Microsoft.Identity.Web library utilizes this key.
92+
// If not removed then it can cause failure to the application.
8693
context.HttpContext.Items.Remove("JwtSecurityTokenUsedToCallWebAPI");
8794
}
8895
}
@@ -93,10 +100,10 @@ public static async Task ProcessGroupsClaimforAccessToken(TokenValidatedContext
93100
/// </summary>
94101
/// <param name="context"></param>
95102
/// <param name="identity"></param>
96-
private static void RemoveExistingClaims(TokenValidatedContext context, ClaimsIdentity identity)
103+
private static void RemoveExistingClaim(ClaimsIdentity identity)
97104
{
98-
//clear existing claim
99-
List<Claim> existingGroupsClaims = context.Principal.Claims.Where(x => x.Type == "groups").ToList();
105+
// clear an existing claim
106+
List<Claim> existingGroupsClaims = identity.Claims.Where(x => x.Type == "groups").ToList();
100107
if (existingGroupsClaims?.Count > 0)
101108
{
102109
foreach (Claim groupsClaim in existingGroupsClaims)
@@ -152,4 +159,4 @@ private static List<Group> ProcessIGraphServiceMemberOfCollectionPage(IUserMembe
152159
return allGroups;
153160
}
154161
}
155-
}
162+
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,19 @@ public void ConfigureServices(IServiceCollection services)
4545
options.Events.OnTokenValidated = async context =>
4646
{
4747
//Calls method to process groups overage claim.
48-
await GraphHelper.ProcessGroupsClaimforAccessToken(context);
48+
await GraphHelper.ProcessClaimsForGroupsOverage(context);
4949
};
5050
}, options => { Configuration.Bind("AzureAd", options); })
5151
.EnableTokenAcquisitionToCallDownstreamApi(options => Configuration.Bind("AzureAd", options))
5252
.AddMicrosoftGraph(Configuration.GetSection("GraphAPI"))
5353
.AddInMemoryTokenCaches();
5454

55-
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options => {
55+
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
56+
{
5657
// The following code instructs the ASP.NET Core middleware to use the data in the "groups" claim in the [Authorize] attribute and for User.IsInRole()
58+
// Uncomment the following if you wish to use groups for roles
5759
// See https://docs.microsoft.com/en-us/aspnet/core/security/authorization/roles for more info.
58-
options.TokenValidationParameters.RoleClaimType = "groups";
60+
// options.TokenValidationParameters.RoleClaimType = "groups";
5961
});
6062

6163
services.AddControllersWithViews(options =>

0 commit comments

Comments
 (0)