My GitHub repo use AAD App Roles & also provides fine-grained access control to data using Resource-based and Policy-based authorization.
Rules
In this example, we want to provide fine-grained access control using Azure Active Directory App Roles & Groups. This way, we don’t have to maintain the list of users and their roles in our application. Instead, we can keep our application focused on “role-based access”.

In this example, there are 2 branches of the company & a corporate office. The following rules should apply:
- Each person should be to read their own salary data
- No person should be able to modify their own salary data
- The regional manager of a branch should be able to read & write all salary data for their branch only
- The CFO should be able to read & write all salary data for all employees (except themselves, of course)
Web App
As you sign in with different users, you will see that they have different permissions.
Salesperson
As someone with the General.ReadWrite role, Dwight can see his own salary, but not his fellow employees. He also cannot create a new salary record or modify his salary.

Regional manager – Scranton
As someone with the RegionalManager.ReadWrite role, Michael can see his own salary and the salaries of his branch employees (but not those of the Stamford branch). He can modify his own employees salaries, but not his own.

Regional manager – Stamford
As someone with the RegionalManager.ReadWrite role, Josh can see his own salary and the salaries of his branch employees (but not those of the Scranton branch). He can modify his own employees salaries, but not his own.

CFO
As someone with the CFO.ReadWrite role, David can see his own salary and the salaries of all his employees. He can modify his employees salaries, but not his own.

How this works
ID token for each user signin
We don’t want to have to constantly issue Graph API queries to find out information about the user, so we can request that every JWT ID token that is presented to the application include the AAD groups they are a part of (that are related to the application), the app roles the user is assigned (by virtue of being in those AAD groups) and the upn of the user (so we can key off it in the database).

Setup authorization service
In the src/DunderMifflinInfinity.API/Startup.cs
file in the ConfigureServices method, we need to set up the authorization policies we want enforced.

- You can group several roles together into a single policy so that you don’t have to specify them multiple times.
- You can define a list of requirements that must all return Succeeded for the policy to pass.
Add a new SalaryAuthorizationHandler
to the list of services.
Authorization Service
In the src/DunderMifflinInfinity.API/AuthorizationHandlers/SalaryAuthorizationHandler.cs
file, the SalaryAuthorizationHandler service is responsible for evaluating the list of requirements defined in the Salary policy.
It will loop through each requirement as defined in the Startup.cs
file. For each requirement, a function will get called to evaluate it.

For the CannotModifyOwnSalaryRequirement, we need to check the User.Identity.Name
to see if it is the same UserPrincipalName
of the Salary object the user is trying to modify. No one should be able to modify their own salary.

For the OnlyManagementCanModifySalariesRequirement, we need to check the roles the signed-in user has to see if they are in a management role.

For the BranchManagerCanOnlyModifyOwnBranchSalariesRequirement, we need to check if the user is a regional manager, and if so, ensure they are only modifying data for their own branch employees.

Note: In this case, the same policies are applied to both the web API & the web app. This means you could abstract out the
AuthorizationHandlers
into a separate library that both projects depend on. However, there are likely to be differences in a real-world application, so they are copied in this case so you have the ability to customize as needed.
API Controllers
Get
In the src/DunderMifflinInfinity.API/Controllers/SalariesController.cs
file, we use two attributes. For the entire controller, we use the RequiredScope to ensure only users have the Salary.ReadWrite scope in their token before continuing. Then in the GetSalaries method, we use the Policies.General policy because everyone can see some salary data, but it will change depending on their role. We use Entity Framework to only pull the appropriate data for each role.

Put
In the src/DunderMifflinInfinity.API/Controllers/SalariesController.cs
file, in the PutSalary method, we use the _authorizationService to evaluate if the signed-in user is allowed to modify the Salary object. If so, we make the database change, otherwise, we forbid it. This will call the SalaryAuthorizationService and loop through all requirements.

Web App Controllers
The Web App controllers delegate accessing the web API to some helper Services (src/DunderMifflinInfinity.WebApp/Services
) that get an access token when needed.
private async Task PrepareAuthenticatedClient()
{
var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(new[] { scope });
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
private async Task<HttpResponseMessage> ExecuteApi(HttpMethod httpMethod, string apiEndpoint, StringContent? jsonContent = null)
{
await PrepareAuthenticatedClient();
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(httpMethod, apiBaseAddress + apiEndpoint){
Content = jsonContent
};
return await httpClient.SendAsync(httpRequestMessage);
}
When the application runs and tries to access the backend API for Salary data, an access token is procured for the Salary.ReadWrite scope.

The access token contains all the information we need to decide if the user should be allowed to modify the data. Notice that we have the aud (audience, the backend API app ID), the groups the user is a part of (that are related to this app), the app roles the user is a part of, the Salary.ReadWrite scope & the upn (user principal name) uniquely identifying the user.

These services are registered in the src/DunderMifflinInfinity.WebApp/Startup.cs
file.
services.AddHttpClient<IBranchApiService, BranchApiService>();
services.AddHttpClient<IEmployeeApiService, EmployeeApiService>();
services.AddHttpClient<ISalaryApiService, SalaryApiService>();
services.AddHttpClient<ISaleApiService, SaleApiService>();
Views
In the src/DunderMifflinInfinity.WebApp/Views/Salaries/Index.cshtml
you can see that we hide the Create new button if the user is not a manager.
We also hide the Edit and Delete buttons if the user is not a manager & is not trying to modify their own salary.
