Skip to content

Commit 1c000f2

Browse files
author
Kalyan Krishna
committed
added a mistakenly deleted file
1 parent db48a2d commit 1c000f2

File tree

2 files changed

+148
-1
lines changed

2 files changed

+148
-1
lines changed

4-WebApp-your-API/4-1-MyOrg/4-1-MyOrg.sln

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DDAC3758-9CAE-4790-84B1-5B6258BAE44E}"
77
ProjectSection(SolutionItems) = preProject
88
README-incremental-instructions.md = README-incremental-instructions.md
9-
README.md = README.md
109
README-use-certificate.md = README-use-certificate.md
10+
README.md = README.md
1111
EndProjectSection
1212
EndProject
1313
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoListService", "TodoListService\TodoListService.csproj", "{A55C5542-9C7B-4A34-932C-05DAF36CED0C}"
@@ -31,6 +31,7 @@ EndProject
3131
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReadmeFiles", "ReadmeFiles", "{2A7989B7-23E0-46D4-8F2A-3D069084F65D}"
3232
ProjectSection(SolutionItems) = preProject
3333
ReadmeFiles\ReadmeAboutTheCode.md = ReadmeFiles\ReadmeAboutTheCode.md
34+
ReadMeAboutTheCode.md = ReadMeAboutTheCode.md
3435
ReadmeFiles\ReadmeAzureDeploy.md = ReadmeFiles\ReadmeAzureDeploy.md
3536
ReadmeFiles\ReadmeExploreTheSample.md = ReadmeFiles\ReadmeExploreTheSample.md
3637
ReadmeFiles\ReadmeHowTheCodeWasCreated.md = ReadmeFiles\ReadmeHowTheCodeWasCreated.md
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
## About the code
2+
3+
<details>
4+
<summary>Expand the section</summary>
5+
6+
1. In the `TodoListService` project, which represents the web api, first the package `Microsoft.Identity.Web`is added from NuGet.
7+
8+
1. Starting with the **Startup.cs** file :
9+
10+
* at the top of the file, the following using directory was added:
11+
12+
```CSharp
13+
using Microsoft.Identity.Web;
14+
```
15+
16+
* in the `ConfigureServices` method, the following code was added, replacing any existing `AddAuthentication()` code:
17+
18+
```CSharp
19+
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
20+
```
21+
22+
* `AddMicrosoftIdentityWebApiAuthentication()` protects the Web API by [validating Access tokens](https://docs.microsoft.com/azure/active-directory/develop/access-tokens#validating-tokens) sent tho this API. Check out [Protected web API: Code configuration](https://docs.microsoft.com/azure/active-directory/develop/scenario-protected-web-api-app-configuration) which explains the inner workings of this method in more detail.
23+
24+
* There is a bit of code (commented) provided under this method that can be used to used do **extended token validation** and do checks based on additional claims, such as:
25+
* check if the client app's `appid (azp)` is in some sort of an allowed list via the 'azp' claim, in case you wanted to restrict the API to a list of client apps.
26+
* check if the caller's account is homed or guest via the `acct` optional claim
27+
* check if the caller belongs to right roles or groups via the `roles` or `groups` claim, respectively
28+
29+
See [How to manually validate a JWT access token using the Microsoft identity platform](https://aka.ms/extendtokenvalidation) for more details on to further verify the caller using this method.
30+
31+
1. Then in the controllers `TodoListController.cs`, the `[Authorize]` added on top of the class to protect this route.
32+
* Further in the controller, the [RequiredScopeOrAppPermission](https://github.com/AzureAD/microsoft-identity-web/wiki/web-apis#checking-for-scopes-or-app-permissions=) is used to list the ([Delegated permissions](https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent)), that the user should consent for, before the method can be called.
33+
* The delegated permissions are checked inside `TodoListService\Controllers\ToDoListController.cs` in the following manner:
34+
35+
```CSharp
36+
[HttpGet]
37+
[RequiredScopeOrAppPermission(
38+
AcceptedScope = new string[] { "ToDoList.Read", "ToDoList.ReadWrite" },
39+
AcceptedAppPermission = new string[] { "ToDoList.Read.All", "ToDoList.ReadWrite.All" }
40+
)]
41+
public IEnumerable<Todo> Get()
42+
{
43+
if (!IsAppOnlyToken())
44+
{
45+
// this is a request for all ToDo list items of a certain user.
46+
return TodoStore.Values.Where(x => x.Owner == _currentLoggedUser);
47+
}
48+
else
49+
{
50+
// Its an app calling with app permissions, so return all items across all users
51+
return TodoStore.Values;
52+
}
53+
}
54+
```
55+
56+
The code above demonstrates that to be able to reach a GET REST operation, the access token should contain AT LEAST ONE of the scopes (delegated permissions) listed inside parameter of [RequiredScopeOrAppPermission](https://github.com/AzureAD/microsoft-identity-web/wiki/web-apis#checking-for-scopes-or-app-permissions=) attribute
57+
Please note that while in this sample, the client app only works with *Delegated Permissions*, the API's controller is designed to work with both *Delegated* and *Application* permissions.
58+
59+
The **ToDoList.<*>.All** permissions are **Application Permissions**.
60+
61+
Here is another example from the same controller:
62+
63+
``` CSharp
64+
[HttpDelete("{id}")]
65+
[RequiredScopeOrAppPermission(
66+
AcceptedScope = new string[] { "ToDoList.ReadWrite" },
67+
AcceptedAppPermission = new string[] { "ToDoList.ReadWrite.All" })]
68+
public void Delete(int id)
69+
{
70+
if (!IsAppOnlyToken())
71+
{
72+
// only delete if the ToDo list item belonged to this user
73+
if (TodoStore.Values.Any(x => x.Id == id && x.Owner == _currentLoggedUser))
74+
{
75+
TodoStore.Remove(id);
76+
}
77+
}
78+
else
79+
{
80+
TodoStore.Remove(id);
81+
}
82+
}
83+
```
84+
85+
The above code demonstrates that to be able to execute the DELETE REST operation, the access token MUST contain the `ToDoList.ReadWrite` scope. Note that the called is not allowed to access this operation with just `ToDoList.Read` scope only.
86+
Also note of how we distinguish the **what** a user can delete. When there is a **ToDoList.ReadWrite.All** permission available, the user can delete **ANY** entity from the database,
87+
but with **ToDoList.ReadWrite**, the user can delete only their own entries.
88+
89+
* The method *IsAppOnlyToken()* is used by controller method to detect presence of an app only token, i.e a token that was issued to an app using the [Client credentials](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow) flow, i.e no users were signed-in by this client app.
90+
91+
```csharp
92+
private bool IsAppOnlyToken()
93+
{
94+
// Add in the optional 'idtyp' claim to check if the access token is coming from an application or user.
95+
//
96+
// See: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims
97+
return HttpContext.User.Claims.Any(c => c.Type == "idtyp" && c.Value == "app");
98+
}
99+
```
100+
101+
1. In the `TodoListClient` project, which represents the client app that signs-in a user and makes calls to the web api, first the package `Microsoft.Identity.Web`is added from NuGet.
102+
103+
* The following lines in *Startup.cs* adds the ability to authenticate a user using Azure AD.
104+
105+
```csharp
106+
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
107+
.EnableTokenAcquisitionToCallDownstreamApi(
108+
Configuration.GetSection("TodoList:TodoListScopes").Get<string>().Split(" ", System.StringSplitOptions.RemoveEmptyEntries)
109+
)
110+
.AddInMemoryTokenCaches();
111+
```
112+
113+
* Specifying Initial scopes (delegated permissions)
114+
115+
The ToDoListClient's *appsettings.json* file contains `ToDoListScopes` key that is used in *startup.cs* to specify which initial scopes (delegated permissions) should be requested for the Access Token when a user is being signed-in:
116+
117+
```csharp
118+
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
119+
.EnableTokenAcquisitionToCallDownstreamApi(Configuration.GetSection("TodoList:TodoListScopes")
120+
.Get<string>().Split(" ", System.StringSplitOptions.RemoveEmptyEntries))
121+
.AddInMemoryTokenCaches();
122+
```
123+
124+
* Detecting *Guest* users of a tenant signing-in. This section of code in *Startup.cs* shows you how to detect if the user signing-in is a *member* or *guest*.
125+
126+
```CSharp
127+
app.Use(async (context, next) => {
128+
if (context.User != null && context.User.Identity.IsAuthenticated)
129+
{
130+
// you can conduct any conditional processing for guest/homes user by inspecting the value of the 'acct' claim
131+
// Read more about the 'acct' claim at aka.ms/optionalclaims
132+
if (context.User.Claims.Any(x => x.Type == "acct"))
133+
{
134+
string claimvalue = context.User.Claims.FirstOrDefault(x => x.Type == "acct").Value;
135+
string userType = claimvalue == "0" ? "Member" : "Guest";
136+
Debug.WriteLine($"The type of the user account from this Azure AD tenant is-{userType}");
137+
}
138+
}
139+
await next();
140+
});
141+
```
142+
143+
1. There is some commented code in *Startup.cs* that also shows how to user certificates and KeyVault in place, see [README-use-certificate](README-use-certificate.md) for more details on how to use code in this section.
144+
1. Also consider adding [MSAL.NET Logging](https://docs.microsoft.com/azure/active-directory/develop/msal-logging-dotnet) to you project
145+
146+
</details>

0 commit comments

Comments
 (0)