Skip to content

Commit 24535fb

Browse files
merged
2 parents 22481c1 + e686f02 commit 24535fb

File tree

2 files changed

+105
-46
lines changed

2 files changed

+105
-46
lines changed

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

Lines changed: 104 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
---
2-
services: active-directory
3-
platforms: dotnet
4-
author: kalyankrishna1
5-
level: 300
6-
client: ASP.NET Core Web App
7-
service: Microsoft Graph
8-
endpoint: Microsoft identity platform
92
page_type: sample
103
languages:
11-
- csharp
4+
- csharp
125
products:
136
- azure
147
- azure-active-directory
158
- dotnet
169
- ms-graph
17-
description: "Add authorization using groups & group claims to an ASP.NET Core Web app that signs-in users with the Microsoft identity platform"
10+
name: Add authorization using groups & group claims to an ASP.NET Core Web app that signs-in users with the Microsoft identity platform
11+
description: "This sample demonstrates a ASP.NET Core Web App application calling The Microsoft Graph"
1812
---
1913

2014
# Add authorization using groups & group claims to an ASP.NET Core Web app that signs-in users with the Microsoft identity platform
@@ -37,21 +31,21 @@ This sample first leverages the ASP.NET Core OpenID Connect middleware to sign i
3731

3832
> An Identity Developer session covered Azure AD App roles and security groups, featuring this scenario and how to handle the overage claim. Watch the video [Using Security Groups and Application Roles in your apps](https://www.youtube.com/watch?v=LRoc-na27l0)
3933
40-
## How to run this sample
41-
42-
To run this sample, you'll need:
34+
## Prerequisites
4335

4436
- [Visual Studio](https://visualstudio.microsoft.com/downloads/)
4537
- An Azure Active Directory (Azure AD) tenant. For more information on how to get an Azure AD tenant, see [How to get an Azure AD tenant](https://azure.microsoft.com/documentation/articles/active-directory-howto-tenant/)
4638
- A user account in your Azure AD tenant. This sample will not work with a **personal Microsoft account**. Therefore, if you signed in to the [Azure portal](https://portal.azure.com) with a personal account and have never created a user account in your directory before, you need to do that now.
4739

4840
> Please make sure to have one or more user accounts in the tenant assigned to a few security groups in your tenant. Please follow the instructions in [Create a basic group and add members using Azure Active Directory](https://docs.microsoft.com/azure/active-directory/fundamentals/active-directory-groups-create-azure-portal) to create a few groups and assign users to them if not already done.
4941
50-
### Step 1: Clone or download this repository
42+
## Setup
43+
44+
### Step 1: Clone or download this repository
5145

5246
From your shell or command line:
5347

54-
```Shell
48+
```console
5549
git clone https://github.com/Azure-Samples/microsoft-identity-platform-aspnetcore-webapp-tutorial.git
5650
```
5751

@@ -65,19 +59,19 @@ Navigate to the `"5-WebApp-AuthZ"` folder
6559
cd 5-WebApp-AuthZ\5-2-Groups
6660
```
6761

68-
### Step 2: Register the sample application with your Azure Active Directory tenant
62+
## Register the sample application with your Azure Active Directory tenant
6963

7064
There is one project in this sample. To register it, you can:
7165

72-
- either follow the step [Choose the Azure AD tenant where you want to create your applications](#choose-the-azure-ad-tenant-where-you-want-to-create-your-applications) below
66+
- either follow the steps below for manually register your apps
7367
- or use PowerShell scripts that:
7468
- **automatically** creates the Azure AD applications and related objects (passwords, permissions, dependencies) for you.
7569
- modify the projects' configuration files.
7670

7771
<details>
7872
<summary>Expand this section if you want to use this automation:</summary>
7973

80-
1. On Windows, run PowerShell and navigate to the root of the cloned directory
74+
1. On Windows, run PowerShell as **Administrator** and navigate to the root of the cloned directory
8175
1. In PowerShell run:
8276

8377
```PowerShell
@@ -97,14 +91,14 @@ There is one project in this sample. To register it, you can:
9791
9892
</details>
9993

100-
Follow the steps below to manually register and configure your application on Azure AD.
94+
Follow the steps below to manually walk through the steps to register and configure the applications in the Azure portal.
10195

102-
#### Choose the Azure AD tenant where you want to create your applications
96+
### Choose the Azure AD tenant where you want to create your applications
10397

10498
As a first step you'll need to:
10599

106100
1. Sign in to the [Azure portal](https://portal.azure.com).
107-
1. If your account is present in more than one Azure AD tenant, select your profile at the top right corner in the menu on top of the page, and then **switch directory**.
101+
1. If your account is present in more than one Azure AD tenant, select your profile at the top right corner in the menu on top of the page, and then **switch directory** to change your portal session to the desired Azure AD tenant.
108102

109103
#### Register the web app (WebApp-GroupClaims)
110104

@@ -126,14 +120,14 @@ As a first step you'll need to:
126120
1. In the app's registration screen, click on the **Certificates & secrets** blade in the left to open the page where we can generate secrets and upload certificates.
127121
1. In the **Client secrets** section, click on **New client secret**:
128122
- Type a key description (for instance `app secret`),
129-
- Select one of the available key durations (**In 1 year**, **In 2 years**, or **Never Expires**) as per your security concerns.
123+
- Select one of the available key durations (**In 1 year**, **In 2 years**, or **Never Expires**) as per your security posture.
130124
- The generated key value will be displayed when you click the **Add** button. Copy the generated value for use in the steps later.
131125
- You'll need this key later in your code's configuration files. This key value will not be displayed again, and is not retrievable by any other means, so make sure to note it from the Azure portal before navigating to any other screen or blade.
132126
1. In the app's registration screen, click on the **API permissions** blade in the left to open the page where we add access to the APIs that your application needs.
133127
- Click the **Add a permission** button and then,
134128
- Ensure that the **Microsoft APIs** tab is selected.
135129
- In the *Commonly used Microsoft APIs* section, click on **Microsoft Graph**
136-
- In the **Delegated permissions** section, select the **GroupMember.Read.All** in the list. Use the search box if necessary.
130+
- In the **Delegated permissions** section, select the **User.Read** and **GroupMember.Read.All** in the list. Use the search box if necessary.
137131
- Click on the **Add permissions** button at the bottom.
138132
1. At this stage permissions are assigned correctly and the **GroupMember.Read.All** requires admin to consent.
139133
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
@@ -197,12 +191,13 @@ Open the project in your IDE (like Visual Studio) to configure the code.
197191
1. Find the app key `Domain` and replace the existing value with your Azure AD tenant name.
198192
1. Find the app key `ClientSecret` and replace the existing value with the key you saved during the creation of the `WebApp-GroupClaims` app, in the Azure portal.
199193

200-
### Step 4: Run the sample
194+
## Running the sample
201195

202196
1. Clean and rebuild the solution, and run it.
203197
1. Open your web browser and make a request to the app. The app immediately attempts to authenticate you to the Microsoft identity platform. You can sign-in with a *work or school account* from the tenant where you created this app. But sign-in with admin for the first time as admin consent is required for `GroupMember.Read.All` permission.
198+
1. If the **Overage** scenario occurs for the signed-in user then all the groups are retrieved from Microsoft Graph and added in a list. The [overage](#groups-overage-claim) scenario is discussed later in this article.
204199
1. On the home page, the app lists the various claims it obtained from your ID token. You'd notice one more claims named `groups`.
205-
1. On the top menu, click on the signed-in user's name **[email protected]**, you should now see all kind of information about yourself including their picture. Beneath that, a list of all the security groups that the signed-in user is assigned to are listed as well. All of this was obtained by making calls to Microsoft Graph. This list is useful if the **Overage** scenario occurs with this signed-in user. The [overage](#groups-overage-claim) scenario is discussed later in this article.
200+
1. On the top menu, click on the signed-in user's name **[email protected]**, you should now see all kind of information about yourself including their picture.
206201

207202
> Did the sample not work for you as expected? Did you encounter issues trying this sample? Then please reach out to us using the [GitHub Issues](../../../../issues) page.
208203
@@ -318,14 +313,8 @@ The following files have the code that would be of interest to you:
318313

319314
1. HomeController.cs
320315
1. Passes the **HttpContext.User** (the signed-in user) to the view.
321-
1. UserProfileController.cs
322-
1. Uses the **IMSGraphService** methods to fetch the signed-in user's group memberships.
323-
1. IMSGraphService.cs, MSGraphService.cs and UserGroupsAndDirectoryRoles.cs
324-
1. Uses the [Microsoft Graph SDK](https://github.com/microsoftgraph/msgraph-sdk-dotnet) to carry out various operations with [Microsoft Graph](https://graph.microsoft.com).
325316
1. Home\Index.cshtml
326317
1. This has some code to print the current user's claims
327-
1. UserProfile\Index.cshtml
328-
1. Has some client code that prints the signed-in user's information obtained from the [/me](https://docs.microsoft.com/graph/api/user-get?view=graph-rest-1.0), [/me/photo](https://docs.microsoft.com/graph/api/profilephoto-get) and [/memberOf](https://docs.microsoft.com/graph/api/user-list-memberof) endpoints.
329318
1. Startup.cs
330319

331320
- at the top of the file, add the following using directive:
@@ -344,43 +333,114 @@ The following files have the code that would be of interest to you:
344333
- have been replaced by these lines:
345334

346335
```CSharp
347-
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
348-
.EnableTokenAcquisitionToCallDownstreamApi( new string[] { "User.Read", "Directory.Read.All" })
349-
.AddInMemoryTokenCaches();
350-
351-
services.AddMSGraphService(Configuration); // Adds the IMSGraphService as an available service for this app.
336+
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
337+
.AddMicrosoftIdentityWebApp(
338+
options =>
339+
{
340+
Configuration.Bind("AzureAd", options);
341+
options.Events = new OpenIdConnectEvents();
342+
options.Events.OnTokenValidated = async context =>
343+
{
344+
await GraphHelper.ProcessClaimsForGroupsOverage(context);
345+
};
346+
}, options => { Configuration.Bind("AzureAd", options); })
347+
.EnableTokenAcquisitionToCallDownstreamApi(options => Configuration.Bind("AzureAd", options), initialScopes)
348+
.AddMicrosoftGraph(Configuration.GetSection("GraphAPI"))
349+
.AddInMemoryTokenCaches();
352350
```
353351

352+
`OnTokenValidated` event calls **ProcessClaimsForGroupsOverage** method, that is defined in GraphHelper.cs, to process groups overage claim.
353+
354+
`AddMicrosoftGraph` registers the service for `GraphServiceClient`. The values for BaseUrl and Scopes defined in `GraphAPI` section of **appsettings.json**.
355+
356+
1. In GraphHelper.cs, ProcessClaimsForGroupsOverage method uses `GraphServiceClient` to retrieve groups for the signed-in user from [/me/memberOf](https://docs.microsoft.com/graph/api/user-list-memberof) endpoint. All the groups are stored in list of claims and the data can be used in the application as per requirement.
357+
358+
```csharp
359+
public static async Task ProcessClaimsForGroupsOverage(TokenValidatedContext context)
360+
{
361+
if (context.Principal.Claims.Any(x => x.Type == "hasgroups" || (x.Type == "_claim_names" && x.Value == "{\"groups\":\"src1\"}")))
362+
{
363+
var graphClient = context.HttpContext.RequestServices.GetService<GraphServiceClient>();
364+
if (graphClient == null)
365+
{
366+
Console.WriteLine("No service for type 'Microsoft.Graph.GraphServiceClient' has been registered.");
367+
}
368+
else if (context.SecurityToken != null)
369+
{
370+
if (!context.HttpContext.Items.ContainsKey("JwtSecurityTokenUsedToCallWebAPI"))
371+
{
372+
context.HttpContext.Items.Add("JwtSecurityTokenUsedToCallWebAPI", context.SecurityToken as JwtSecurityToken);
373+
}
374+
string select = "id,displayName,onPremisesNetBiosName,onPremisesDomainName,onPremisesSamAccountNameonPremisesSecurityIdentifier";
375+
IUserMemberOfCollectionWithReferencesPage memberPage = new UserMemberOfCollectionWithReferencesPage();
376+
try
377+
{
378+
memberPage = await graphClient.Me.MemberOf.Request().Select(select).GetAsync().ConfigureAwait(false);
379+
}
380+
catch(Exception graphEx)
381+
{
382+
var exMsg = graphEx.InnerException != null ? graphEx.InnerException.Message : graphEx.Message;
383+
Console.WriteLine("Call to Microsoft Graph failed: "+ exMsg);
384+
}
385+
if (memberPage?.Count > 0)
386+
{
387+
var allgroups = ProcessIGraphServiceMemberOfCollectionPage(memberPage);
388+
if (allgroups?.Count > 0)
389+
{
390+
var identity = (ClaimsIdentity)context.Principal.Identity;
391+
if (identity != null)
392+
{
393+
RemoveExistingClaims(identity);
394+
List<Claim> groupClaims = new List<Claim>();
395+
foreach (Group group in allgroups)
396+
{
397+
groupClaims.Add(new Claim("groups", group.Id));
398+
}
399+
}
400+
}
401+
}
402+
}
403+
}
404+
....
405+
}
406+
```
407+
408+
1. UserProfile\Index.cshtml
409+
1. Has some client code that prints the signed-in user's information obtained from the [/me](https://docs.microsoft.com/graph/api/user-get?view=graph-rest-1.0) and [/me/photo](https://docs.microsoft.com/graph/api/profilephoto-get) endpoints by using `GraphServiceClient`.
410+
354411
## How to deploy this sample to Azure
355412

356413
This project has one WebApp project. To deploy that to Azure Web Sites, you'll need to:
357414

358415
- create an Azure Web Site
359-
- publish the Web App / Web APIs to the web site, and
360-
- update its client(s) to call the web site instead of IIS Express.
416+
- publish the project to the web site, and
417+
- update its client(s) to call the web site instead of the local environment.
361418

362419
### Create and publish the `WebApp-GroupClaims` to an Azure Web Site
363420

364421
1. Sign in to the [Azure portal](https://portal.azure.com).
365422
1. Click `Create a resource` in the top left-hand corner, select **Web** --> **Web App**, and give your web site a name, for example, `WebApp-GroupClaims-contoso.azurewebsites.net`.
366-
1. Thereafter select the `Subscription`, `Resource Group`, `App service plan and Location`. `OS` will be **Windows** and `Publish` will be **Code**.
423+
1. Next, select the `Subscription`, `Resource Group`, `App service plan and Location`. `OS` will be **Windows** and `Publish` will be **Code**.
367424
1. Click `Create` and wait for the App Service to be created.
368425
1. Once you get the `Deployment succeeded` notification, then click on `Go to resource` to navigate to the newly created App service.
369426
1. Once the web site is created, locate it it in the **Dashboard** and click it to open **App Services** **Overview** screen.
370-
1. From the **Overview** tab of the App Service, download the publish profile by clicking the **Get publish profile** link and save it. Other deployment mechanisms, such as from source control, can also be used.
427+
1. From the **Overview** tab of the App Service, download the publish profile by clicking the **Get publish profile** link and save it. Other deployment mechanisms, such as from **source control**, can also be used.
371428
1. Switch to Visual Studio and go to the WebApp-GroupClaims project. Right click on the project in the Solution Explorer and select **Publish**. Click **Import Profile** on the bottom bar, and import the publish profile that you downloaded earlier.
372-
1. Click on **Configure** and in the `Connection tab`, update the Destination URL so that it is a `https` in the home page url, for example [https://WebApp-GroupClaims-contoso.azurewebsites.net](https://WebApp-GroupClaims-contoso.azurewebsites.net). Click **Next**.
429+
1. Click on **Configure** and in the `Connection tab`, update the Destination URL so that it is a `https` in the home page URL, for example [https://WebApp-GroupClaims-contoso.azurewebsites.net](https://WebApp-GroupClaims-contoso.azurewebsites.net). Click **Next**.
373430
1. On the Settings tab, make sure `Enable Organizational Authentication` is NOT selected. Click **Save**. Click on **Publish** on the main screen.
374431
1. Visual Studio will publish the project and automatically open a browser to the URL of the project. If you see the default web page of the project, the publication was successful.
375432

376-
### Update the Active Directory tenant application registration for `WebApp-GroupClaims`
433+
### Update the Azure AD app registration for `WebApp-GroupClaims`
377434

378435
1. Navigate back to the [Azure portal](https://portal.azure.com).
379436
In the left-hand navigation pane, select the **Azure Active Directory** service, and then select **App registrations (Preview)**.
380-
1. In the resultant screen, select the `WebApp-GroupClaims` application.
381-
1. In the **Authentication** | page for your application, update the Logout URL fields with the address of your service, for example [https://WebApp-GroupClaims-contoso.azurewebsites.net](https://WebApp-GroupClaims-contoso.azurewebsites.net)
437+
1. In the resulting screen, select the `WebApp-GroupClaims` application.
438+
1. In the **Authentication** page for your application, update the Logout URL fields with the address of your service, for example [https://WebApp-GroupClaims-contoso.azurewebsites.net](https://WebApp-GroupClaims-contoso.azurewebsites.net)
382439
1. From the *Branding* menu, update the **Home page URL**, to the address of your service, for example [https://WebApp-GroupClaims-contoso.azurewebsites.net](https://WebApp-GroupClaims-contoso.azurewebsites.net). Save the configuration.
383-
1. Add the same URL in the list of values of the *Authentication -> Redirect URIs* menu. If you have multiple redirect urls, make sure that there a new entry using the App service's Uri for each redirect url.
440+
1. Add the same URL in the list of values of the *Authentication -> Redirect URIs* menu. If you have multiple redirect URIs, make sure that there a new entry using the App service's URI for each redirect URIs.
441+
442+
> :warning: If your app is using an *in-memory* storage, **Azure App Services** will spin down your web site if it is inactive, and any records that your app was keeping will emptied.
443+
In addition, if you increase the instance count of your web site, requests will be distributed among the instances. Your app's records, therefore, will not be the same on each instance.
384444

385445
## Community Help and Support
386446

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,8 @@ public static async Task<List<string>> GetSignedInUsersGroups(TokenValidatedCont
119119
/// <summary>
120120
/// Remove groups claims if already exists.
121121
/// </summary>
122-
/// <param name="context"></param>
123122
/// <param name="identity"></param>
124-
private static void RemoveExistingClaims(ClaimsIdentity identity)
123+
private static void RemoveExistingGroupsClaims(ClaimsIdentity identity)
125124
{
126125
//clear existing claim
127126
List<Claim> existingGroupsClaims = identity.Claims.Where(x => x.Type == "groups").ToList();

0 commit comments

Comments
 (0)