Skip to content

Commit b77ea1d

Browse files
author
Kalyan Krishna
authored
Merge pull request #46 from Azure-Samples/kkrishna/updates2021
Metadata resiliency updates incorporated
2 parents 37e4dfa + 85063f1 commit b77ea1d

File tree

10 files changed

+325
-128
lines changed

10 files changed

+325
-128
lines changed

README.md

Lines changed: 113 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,60 @@
11
---
2+
page_type: sample
3+
languages:
4+
- csharp
5+
products:
6+
- aspnet-core
7+
- microsoft-identity-web
8+
- azure-active-directory
9+
name: How to manually validate a JWT access token using the Microsoft identity platform
10+
urlFragment: active-directory-dotnet-webapi-manual-jwt-validation
211
services: active-directory
3-
platforms: dotnet
12+
platforms: dotnetcore
413
author: kalyankrishna1
514
level: 300
615
client: .NET Desktop App (WPF)
716
service: ASP.NET Web API
817
endpoint: AAD v2.0
918
---
1019

11-
# How to manually validate a JWT access token using the Microsoft identity platform (formerly Azure Active Directory for developers)
20+
# How to manually validate a JWT access token using the Microsoft identity platform
21+
22+
- [Overview](#overview)
23+
- [About this sample](#about-this-sample)
24+
- [Scenario: protecting a Web API - acquiring a token for the protected Web API](#scenario-protecting-a-web-api---acquiring-a-token-for-the-protected-web-api)
25+
- [Token Validation](#token-validation)
26+
- [What to validate?](#what-to-validate)
27+
- [Validating the claims](#validating-the-claims)
28+
- [Prerequisites](#prerequisites)
29+
- [Setup](#setup)
30+
- [Step 1: Clone or download this repository](#step-1--clone-or-download-this-repository)
31+
- [Register the sample application(s) with your Azure Active Directory tenant](#register-the-sample-applications-with-your-azure-active-directory-tenant)
32+
- [Choose the Azure AD tenant where you want to create your applications](#choose-the-azure-ad-tenant-where-you-want-to-create-your-applications)
33+
- [Register the service app (TodoListService-ManualJwt)](#register-the-service-app-todolistservice-manualjwt)
34+
- [Register the client app (TodoListClient-ManualJwt)](#register-the-client-app-todolistclient-manualjwt)
35+
- [Running the sample](#running-the-sample)
36+
- [Explore the sample](#explore-the-sample)
37+
- [About The Code](#about-the-code)
38+
- [Providing your own Custom token validation handler](#providing-your-own-custom-token-validation-handler)
39+
- [How To Recreate This Sample](#how-to-recreate-this-sample)
40+
- [Creating the TodoListService-ManualJwt Project](#creating-the-todolistservice-manualjwt-project)
41+
- [Creating the TodoListClient Project](#creating-the-todolistclient-project)
42+
- [How to deploy this sample to Azure](#how-to-deploy-this-sample-to-azure)
43+
- [Create and publish the `TodoListService-ManualJwt` to an Azure Web Site](#create-and-publish-the-todolistservice-manualjwt-to-an-azure-web-site)
44+
- [Update the Active Directory tenant application registration for `TodoListService-ManualJwt`](#update-the-active-directory-tenant-application-registration-for-todolistservice-manualjwt)
45+
- [Update the `TodoListClient-ManualJwt` to call the `TodoListService-ManualJwt` Running in Azure Web Sites](#update-the-todolistclient-manualjwt-to-call-the-todolistservice-manualjwt-running-in-azure-web-sites)
46+
- [Azure Government Deviations](#azure-government-deviations)
47+
- [Troubleshooting](#troubleshooting)
48+
- [Community Help and Support](#community-help-and-support)
49+
- [Contributing](#contributing)
50+
- [More information](#more-information)
1251

1352
![Build badge](https://identitydivision.visualstudio.com/_apis/public/build/definitions/a7934fdd-dcde-4492-a406-7fad6ac00e17/18/badge)
1453

54+
## Overview
55+
56+
This sample demonstrates how to manually validate an access token issued to a web API protected by the Microsoft Identity Platform. Here a .NET Desktop App (WPF) calls a protected ASP.NET Web API that is secured using Azure AD.
57+
1558
## About this sample
1659

1760
A Web API that accepts bearer token as a proof of authentication is secured by [validating the token](https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens#validating-tokens) they receive from the callers. When a developer generates a skeleton Web API code using [Visual Studio](https://aka.ms/vsdownload), token validation libraries and code to carry out basic token validation is automatically generated for the project. An example of the generated code using the [asp.net security middleware](https://github.com/aspnet/Security) and [Microsoft Identity Model Extension for .NET](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) to validate tokens is provided below.
@@ -205,7 +248,7 @@ Open the project in your IDE (like Visual Studio or Visual Studio Code) to confi
205248
1. Open the `TodoListClient\App.Config` file.
206249
1. Find the key `ida:Tenant` and replace the existing value with your Azure AD tenant name.
207250
1. Find the key `ida:ClientId` and replace the existing value with the application ID (clientId) of `TodoListClient-ManualJwt` app copied from the Azure portal.
208-
1. Find the key `todo:TodoListResourceId` and replace the value with the App ID URI you registered earlier, when exposing an API. For instance use `api://<application_id>`.
251+
1. Find the key `todo:TodoListResourceId` and replace the existing value with the App ID URI you registered earlier, when exposing an API. For instance use `api://<application_id>`.
209252
1. Find the key `todo:TodoListBaseAddress` and replace the existing value with the base address of `TodoListService-ManualJwt` (by default `https://localhost:44324`).
210253

211254
## Running the sample
@@ -214,32 +257,89 @@ Open the project in your IDE (like Visual Studio or Visual Studio Code) to confi
214257
>
215258
> Clean the solution, rebuild the solution, and run it. You might want to go into the solution properties and set both projects as startup projects, with the service project starting first.
216259
260+
## Explore the sample
261+
217262
Explore the sample by signing in, adding items to the To Do list, removing the user account, and starting again. Notice that if you stop the application without removing the user account, the next time you run the application you won't be prompted to sign in again - that is the sample implements a [persistent cache for MSAL](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/token-cache-serialization), and remembers the tokens from the previous run.
218263

219-
> 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.
264+
> :information_source: 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.
220265
221266
> [Consider taking a moment to share your experience with us.](https://forms.office.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR73pcsbpbxNJuZCMKN0lURpUMjFRQjA0RElFUFNPV0dCUVBGQzk0QkhKTiQlQCN0PWcu)
222267
223268
## About The Code
224269

225-
The manual JWT validation occurs in the [TokenValidationHandler](https://github.com/Azure-Samples/active-directory-dotnet-webapi-manual-jwt-validation/blob/master/TodoListService-ManualJwt/Global.asax.cs#L58) implementation in the `Global.aspx.cs` file in the TodoListService-ManualJwt project. Each time a call is made to the web API, the [TokenValidationHandler.SendAsync()](https://github.com/Azure-Samples/active-directory-dotnet-webapi-manual-jwt-validation/blob/4b80657c5506c8cb30af67b9f61bb6aa68dfca58/TodoListService-ManualJwt/Global.asax.cs#L80) handler is executed:
270+
The manual JWT validation occurs in the [TokenValidationHandler](https://github.com/Azure-Samples/active-directory-dotnet-webapi-manual-jwt-validation/blob/master/TodoListService-ManualJwt/Global.asax.cs#L58) implementation in the `Global.aspx.cs` file in the TodoListService-ManualJwt project.
271+
Each time a call is made to the web API, the [TokenValidationHandler.SendAsync()](https://github.com/Azure-Samples/active-directory-dotnet-webapi-manual-jwt-validation/blob/4b80657c5506c8cb30af67b9f61bb6aa68dfca58/TodoListService-ManualJwt/Global.asax.cs#L80) handler is executed:
226272

227273
This method:
228274

229-
1. gets the token from the Authorization headers
230-
1. gets the open ID configuration from the Azure AD discovery endpoint
231-
1. ensures that the web API is consented to and provisioned in the Azure AD tenant from where the access token originated
232-
1. verifies that the token has not expired
233-
1. sets the parameters to validate:
275+
1. Gets the token from the Authorization headers
276+
1. Gets the open ID configuration, including keys from the Azure AD discovery endpoint
277+
1. Sets the parameters to validate in `GetTokenValidationParameters()`
234278
- the audience - the application accepts both its App ID URI and its AppID/clientID
235279
- the valid issuers - the application accepts both Azure AD V1 and Azure AD V2
236-
237-
1. then it delegates to the `JwtSecurityTokenHandler` class (provided by the `Microsoft.IdentityModel.Tokens` library)
280+
1. Then the token is validated
281+
1. An asp.net claims principal is created after a successful validation
282+
1. ensures that the web API is consented to and provisioned in the Azure AD tenant from where the access token originated
283+
1. Finally, a check for scopes that the web API expects from the caller is carried out
238284

239285
The `TokenValidationHandler` class is registered with ASP.NET in the `TodoListService-ManualJwt/Global.asx.cs` file, in the [Application_Start()](https://github.com/Azure-Samples/active-directory-dotnet-webapi-manual-jwt-validation/blob/4b80657c5506c8cb30af67b9f61bb6aa68dfca58/TodoListService-ManualJwt/Global.asax.cs#L54) method.
240286

241287
For more validation options, please refer to [TokenValidationParameters.cs](https://docs.microsoft.com/dotnet/api/microsoft.identitymodel.tokens.tokenvalidationparameters?view=azure-dotnet)
242288

289+
### Providing your own Custom token validation handler
290+
291+
If you do not wish to control the token validation from its very beginning to the end as laid out in the `Global.asax.cs`, but only limit yourself to validate business logic based on claims in the presented token, you can craft a Custom token handler as provided in the example below.
292+
The provided example, validates to allow callers from a list of whitelisted tenants only.
293+
294+
1. Create a custom handler implementation
295+
296+
```CSharp
297+
298+
public class CustomTokenHandler : JwtSecurityTokenHandler
299+
{
300+
public override ClaimsPrincipal ValidateToken(
301+
string token, TokenValidationParameters validationParameters,
302+
out SecurityToken validatedToken)
303+
{
304+
try
305+
{
306+
var claimsPrincipal = base.ValidateToken(token, validationParameters, out validatedToken);
307+
308+
string[] allowedTenants = { "14c2f153-90a7-4689-9db7-9543bf084dad", "af8cc1a0-d2aa-4ca7-b829-00d361edb652", "979f4440-75dc-4664-b2e1-2cafa0ac67d1" };
309+
string tenantId = claimsPrincipal.Claims.FirstOrDefault(x => x.Type == "tid" || x.Type == "http://schemas.microsoft.com/identity/claims/tenantid")?.Value;
310+
311+
if (!allowedTenants.Contains(tenantId))
312+
{
313+
throw new Exception("This tenant is not authorized");
314+
}
315+
316+
return claimsPrincipal;
317+
}
318+
catch (Exception e)
319+
{
320+
throw;
321+
}
322+
}
323+
}
324+
325+
```
326+
327+
1. Assign the custom handler in `Startup.Auth.cs`
328+
329+
```CSharp
330+
331+
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
332+
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
333+
{
334+
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
335+
TokenValidationParameters = new TokenValidationParameters
336+
{
337+
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
338+
},
339+
TokenHandler = new CustomTokenHandler()
340+
});
341+
```
342+
243343
## How To Recreate This Sample
244344

245345
First, in Visual Studio 2017 create an empty solution to host the projects. Then, follow these steps to create each project.
@@ -351,7 +451,7 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope
351451

352452
## More information
353453

354-
- [Microsoft identity platform (Azure Active Directory for developers)](https://docs.microsoft.com/en-us/azure/active-directory/develop/)
454+
- [Microsoft identity platform (Azure Active Directory for developers)](https://docs.microsoft.com/azure/active-directory/develop/)
355455
- [MSAL.NET's conceptual documentation](https://aka.ms/msal-net)
356456
- [Quickstart: Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app)
357457
- [Quickstart: Configure a client application to access web APIs](https://docs.microsoft.com/azure/active-directory/develop/quickstart-configure-app-access-web-apis)

TodoListClient/TodoListClient-ManualJwt.csproj

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@
6363
<Generator>MSBuild:Compile</Generator>
6464
<SubType>Designer</SubType>
6565
</ApplicationDefinition>
66+
<Compile Include="..\TodoListService-ManualJwt\Models\TodoItem.cs">
67+
<Link>TodoItem.cs</Link>
68+
</Compile>
6669
<Compile Include="FileCache.cs" />
6770
<Page Include="MainWindow.xaml">
6871
<Generator>MSBuild:Compile</Generator>
@@ -107,12 +110,6 @@
107110
<SubType>Designer</SubType>
108111
</None>
109112
</ItemGroup>
110-
<ItemGroup>
111-
<ProjectReference Include="..\Shared\Shared.csproj">
112-
<Project>{f4e8b876-762e-4eec-99bc-7ef12b01799d}</Project>
113-
<Name>Shared</Name>
114-
</ProjectReference>
115-
</ItemGroup>
116113
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
117114
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
118115
Other similar extension points exist, see Microsoft.Common.targets.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Web;
5+
6+
namespace TodoListService_ManualJwt.App_Start
7+
{
8+
public class Startup
9+
{
10+
}
11+
}

TodoListService-ManualJwt/Controllers/TodoListController.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public IEnumerable<TodoItem> Get()
4545
CheckExpectedClaim();
4646

4747
// A user's To Do list is keyed off of the NameIdentifier claim, which contains an immutable, unique identifier for the user.
48-
Claim subject = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier);
48+
Claim subject = ClaimsPrincipal.Current.FindFirst(ClaimConstants.Name);
4949

5050
return from todo in todoBag
5151
where todo.Owner == subject.Value
@@ -59,7 +59,7 @@ public void Post(TodoItem todo)
5959

6060
if (null != todo && !string.IsNullOrWhiteSpace(todo.Title))
6161
{
62-
todoBag.Add(new TodoItem { Title = todo.Title, Owner = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value });
62+
todoBag.Add(new TodoItem { Title = todo.Title, Owner = ClaimsPrincipal.Current.FindFirst(ClaimConstants.Name).Value });
6363
}
6464
}
6565

@@ -74,7 +74,7 @@ private void CheckExpectedClaim()
7474
// The Scope claim tells you what permissions the client application has in the service.
7575
// In this case we look for a scope value of access_as_user, or full access to the service as the user.
7676

77-
if (!ClaimsPrincipal.Current.HasClaim(ClaimConstants.ScopeClaimType, ClaimConstants.ScopeClaimValue))
77+
if (!ClaimsPrincipal.Current.HasClaim(ClaimConstants.ScpClaimType, ClaimConstants.ScopeClaimValue))
7878
{
7979
throw new HttpResponseException(
8080
new HttpResponseMessage {

0 commit comments

Comments
 (0)