Skip to content

Commit b9207cc

Browse files
author
Kalyan Krishna
authored
Merge pull request #25 from Azure-Samples/samplesupdate
Updated "System.IdentityModel.Tokens.Jwt" to "5.2.4" and minor typos addressed
2 parents d0301f3 + ce85260 commit b9207cc

File tree

8 files changed

+124
-87
lines changed

8 files changed

+124
-87
lines changed

AppCreationScripts/Configure.ps1

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
[CmdletBinding()]
2+
param(
3+
[PSCredential] $Credential,
4+
[Parameter(HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
5+
[string] $tenantId
6+
)
7+
18
<#
29
This script creates the Azure AD applications needed for this sample and updates the configuration files
310
for the visual Studio projects from the data in the Azure AD applications.
@@ -8,6 +15,10 @@
815
2) in the PowerShell window, type: Install-Module AzureAD
916
1017
There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script.
18+
19+
# Parameters
20+
# $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
21+
# into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
1122
#>
1223

1324
# Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure
@@ -55,7 +66,7 @@ Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requ
5566
{
5667
AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope"
5768
}
58-
69+
5970
# $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application
6071
if ($requiredApplicationPermissions)
6172
{
@@ -92,18 +103,7 @@ Function ConfigureApplications
92103
This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the
93104
configuration files in the client and service project of the visual studio solution (App.Config and Web.Config)
94105
so that they are consistent with the Applications parameters
95-
#>
96-
[CmdletBinding()]
97-
param(
98-
[PSCredential] $Credential,
99-
[Parameter(HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
100-
[string] $tenantId
101-
)
102-
103-
process
104-
{
105-
# $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
106-
# into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
106+
#>
107107

108108
# Login to Azure PowerShell (interactive if credentials are not already provided:
109109
# you'll need to sign-in with creds enabling your to create apps in the tenant)
@@ -140,6 +140,12 @@ Function ConfigureApplications
140140

141141
$currentAppId = $serviceAadApplication.AppId
142142
$serviceServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp}
143+
144+
# add this user as app owner
145+
$user = Get-AzureADUser -ObjectId $creds.Account.Id
146+
Add-AzureADApplicationOwner -ObjectId $serviceAadApplication.ObjectId -RefObjectId $user.ObjectId
147+
Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($serviceAadApplication.DisplayName)'"
148+
143149
Write-Host "Done."
144150

145151
# URL of the AAD application in the Azure portal
@@ -155,6 +161,11 @@ Function ConfigureApplications
155161

156162
$currentAppId = $clientAadApplication.AppId
157163
$clientServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp}
164+
165+
# add this user as app owner
166+
Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId -RefObjectId $user.ObjectId
167+
Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'"
168+
158169
Write-Host "Done."
159170

160171
# URL of the AAD application in the Azure portal
@@ -170,6 +181,13 @@ Function ConfigureApplications
170181
Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess
171182
Write-Host "Granted."
172183

184+
# Configure known client applications for service
185+
Write-Host "Configure known client applications for the 'service'"
186+
$knowApplications = New-Object System.Collections.Generic.List[System.String]
187+
$knowApplications.Add($clientAadApplication.AppId)
188+
Set-AzureADApplication -ObjectId $serviceAadApplication.ObjectId -KnownClientApplications $knowApplications
189+
Write-Host "Configured."
190+
173191
# Update config file for 'service'
174192
$configFile = $pwd.Path + "\..\TodoListService-ManualJwt\Web.Config"
175193
Write-Host "Updating the sample code ($configFile)"
@@ -186,8 +204,7 @@ Function ConfigureApplications
186204
ReplaceSetting -configFilePath $configFile -key "todo:TodoListResourceId" -newValue $serviceAadApplication.IdentifierUris
187205
ReplaceSetting -configFilePath $configFile -key "todo:TodoListBaseAddress" -newValue $serviceAadApplication.HomePage
188206
Add-Content -Value "</tbody></table></body></html>" -Path createdApps.html
189-
190-
}
207+
191208
}
192209

193210

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,14 @@ A token represents the outcome of an authentication operation with some artifact
2727

2828
With Azure Active Directory taking the full responsibility of verifying user's raw credentials, the token receiver's responsibility shifts from verifying raw credentials to verifying that their caller did indeed go through your identity provider of choice and successfully authenticated. The identity provider represents successful authentication operations by issuing a token, hence the job now becomes to validate that token.
2929

30+
### What to validate
31+
While you should always validate tokens issued to the resources (audience) that you are developineg, your application will also obtain access tokens for other resources from AAD. AAD will provide an access token in whatever token format that is appropriate to that resource.
32+
This access token itself should be treated like an opaque blob by your application, as your app isn’t the access token’s intended audience and thus your app should not bother itself with looking into the contents of this access token.
33+
Your app should just pass it in the call to the resource. It's the called resource's responsibility to validate this access token token.
34+
3035
### Validating the claims
3136

32-
When an application receives an ID token upon user sign-in, it should also perform a few checks against the claims in the ID token. These verifications include but are not limited to:
37+
When an application receives an access token upon user sign-in, it should also perform a few checks against the claims in the access token. These verifications include but are not limited to:
3338

3439
- **audience** claim, to verify that the ID token was intended to be given to your application
3540
- **not before** and "expiration time" claims, to verify that the ID token has not expired
@@ -95,7 +100,7 @@ of the Azure Active Directory window respectively as *Name* and *Directory ID*
95100
#### Register the TodoListClient client app
96101

97102
1. Click on **App registrations** and choose **New application registration**.
98-
1. Enter a friendly name for the application, for example 'TodoListClient-DotNet' and select 'Native' as the Application Type. For the redirect URI, enter `https://TodoListClient`. Please note that the Redirect URI will not be used in this sample, but it needs to be defined nonetheless. Click on **Create** to create the application.
103+
1. Enter a friendly name for the application, for example 'TodoListClient-ManualJwt' and select 'Native' as the Application Type. For the redirect URI, enter `https://TodoListClient`. Please note that the Redirect URI will not be used in this sample, but it needs to be defined nonetheless. Click on **Create** to create the application.
99104
1. In the succeeding page, Find the **Application ID** value and copy it to the clipboard.
100105
1. Then click on **Settings** and choose **Properties**.
101106
1. Configure Permissions for your application - in the Settings menu, choose the **Required permissions** section, click on **Add**, then **Select an API**, and type 'TodoListService' in the textbox and hit enter. Select 'TodoListService-ManualJwt' from the results and click the 'Select' button. Then, click on **Select Permissions** and select 'Access TodoListService-ManualJwt'. Click the 'Select' button again to close this screen. Click on **Done** to finish adding the permission.

TodoListClient/App.config

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<configuration>
3-
<startup>
4-
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
5-
</startup>
3+
<startup>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
5+
</startup>
66
<appSettings>
77
<add key="ida:Tenant" value="[Enter tenant name, e.g. contoso.onmicrosoft.com]" />
88
<add key="ida:ClientId" value="[Enter client ID as obtained from Azure Portal, e.g. 82692da5-a86f-44c9-9d53-2f88d52b478b]" />

TodoListClient/FileCache.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ class FileCache : TokenCache
3737
private static readonly object FileLock = new object();
3838

3939
// Initializes the cache against a local file.
40-
// If the file is already rpesent, it loads its content in the ADAL cache
40+
// If the file is already present, it loads its content in the ADAL cache
4141
public FileCache(string filePath=@".\TokenCache.dat")
4242
{
43-
CacheFilePath = filePath;
44-
this.AfterAccess = AfterAccessNotification;
45-
this.BeforeAccess = BeforeAccessNotification;
43+
this.CacheFilePath = filePath;
44+
this.AfterAccess = this.AfterAccessNotification;
45+
this.BeforeAccess = this.BeforeAccessNotification;
4646
lock (FileLock)
4747
{
48-
this.Deserialize(File.Exists(CacheFilePath) ? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath), null, DataProtectionScope.CurrentUser) : null);
48+
this.Deserialize(File.Exists(this.CacheFilePath) ? ProtectedData.Unprotect(File.ReadAllBytes(this.CacheFilePath), null, DataProtectionScope.CurrentUser) : null);
4949
}
5050
}
5151

@@ -62,7 +62,7 @@ void BeforeAccessNotification(TokenCacheNotificationArgs args)
6262
{
6363
lock (FileLock)
6464
{
65-
this.Deserialize(File.Exists(CacheFilePath) ? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),null,DataProtectionScope.CurrentUser) : null);
65+
this.Deserialize(File.Exists(this.CacheFilePath) ? ProtectedData.Unprotect(File.ReadAllBytes(this.CacheFilePath),null,DataProtectionScope.CurrentUser) : null);
6666
}
6767
}
6868

@@ -75,7 +75,7 @@ void AfterAccessNotification(TokenCacheNotificationArgs args)
7575
lock (FileLock)
7676
{
7777
// reflect changes in the persistent store
78-
File.WriteAllBytes(CacheFilePath, ProtectedData.Protect(this.Serialize(),null,DataProtectionScope.CurrentUser));
78+
File.WriteAllBytes(this.CacheFilePath, ProtectedData.Protect(this.Serialize(),null,DataProtectionScope.CurrentUser));
7979
// once the write operation took place, restore the HasStateChanged bit to false
8080
this.HasStateChanged = false;
8181
}

TodoListService-ManualJwt/Global.asax.cs

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,28 @@
1414
// limitations under the License.
1515
//----------------------------------------------------------------------------------------------
1616

17+
using Microsoft.IdentityModel.Protocols;
18+
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
19+
using Microsoft.IdentityModel.Tokens;
1720
using System;
1821
using System.Collections.Generic;
19-
using System.Linq;
22+
using System.Configuration;
23+
using System.Globalization;
24+
using System.IdentityModel.Tokens.Jwt;
25+
using System.Net;
26+
27+
// The following using statements were added for this sample.
28+
using System.Net.Http;
29+
using System.Net.Http.Headers;
30+
using System.Security.Claims;
31+
using System.Threading;
32+
using System.Threading.Tasks;
2033
using System.Web;
2134
using System.Web.Http;
2235
using System.Web.Mvc;
2336
using System.Web.Optimization;
2437
using System.Web.Routing;
2538

26-
// The following using statements were added for this sample.
27-
using System.Net.Http;
28-
using System.IdentityModel.Tokens;
29-
using System.Threading.Tasks;
30-
using System.Threading;
31-
using System.Net;
32-
using System.IdentityModel.Selectors;
33-
using System.Security.Claims;
34-
using System.Net.Http.Headers;
35-
using System.IdentityModel.Metadata;
36-
using System.ServiceModel.Security;
37-
using System.Xml;
38-
using System.Security.Cryptography.X509Certificates;
39-
using System.Globalization;
40-
using System.Configuration;
41-
using Microsoft.IdentityModel.Protocols;
42-
4339
namespace TodoListService_ManualJwt
4440
{
4541
public class WebApiApplication : System.Web.HttpApplication
@@ -63,17 +59,18 @@ internal class TokenValidationHandler : DelegatingHandler
6359
// The Authority is the sign-in URL of the tenant.
6460
// The Audience is the value the service expects to see in tokens that are addressed to it.
6561
//
66-
static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
67-
static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
68-
static string audience = ConfigurationManager.AppSettings["ida:Audience"];
69-
static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
70-
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
71-
72-
static string _issuer = string.Empty;
73-
static List<SecurityToken> _signingTokens = null;
74-
static DateTime _stsMetadataRetrievalTime = DateTime.MinValue;
75-
static string scopeClaimType = "http://schemas.microsoft.com/identity/claims/scope";
76-
62+
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
63+
64+
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
65+
private static string audience = ConfigurationManager.AppSettings["ida:Audience"];
66+
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
67+
private string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
68+
69+
private static string _issuer = string.Empty;
70+
private static ICollection<SecurityKey> _signingKeys = null;
71+
private static DateTime _stsMetadataRetrievalTime = DateTime.MinValue;
72+
private static string scopeClaimType = "http://schemas.microsoft.com/identity/claims/scope";
73+
7774
//
7875
// SendAsync checks that incoming requests have a valid access token, and sets the current user identity using that access token.
7976
//
@@ -89,32 +86,32 @@ protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
8986

9087
if (jwtToken == null)
9188
{
92-
HttpResponseMessage response = BuildResponseErrorMessage(HttpStatusCode.Unauthorized);
89+
HttpResponseMessage response = this.BuildResponseErrorMessage(HttpStatusCode.Unauthorized);
9390
return response;
9491
}
9592

9693
string issuer;
97-
List<SecurityToken> signingTokens;
94+
ICollection<SecurityKey> signingKeys;
9895

9996
try
10097
{
101-
// The issuer and signingTokens are cached for 24 hours. They are updated if any of the conditions in the if condition is true.
98+
// The issuer and signingKeys are cached for 24 hours. They are updated if any of the conditions in the if condition is true.
10299
if (DateTime.UtcNow.Subtract(_stsMetadataRetrievalTime).TotalHours > 24
103100
|| string.IsNullOrEmpty(_issuer)
104-
|| _signingTokens == null)
101+
|| _signingKeys == null)
105102
{
106103
// Get tenant information that's used to validate incoming jwt tokens
107-
string stsDiscoveryEndpoint = string.Format("{0}/.well-known/openid-configuration", authority);
108-
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
109-
OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync();
104+
string stsDiscoveryEndpoint = $"{this.authority}/.well-known/openid-configuration";
105+
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
106+
var config = await configManager.GetConfigurationAsync(cancellationToken);
110107
_issuer = config.Issuer;
111-
_signingTokens = config.SigningTokens.ToList();
112-
108+
_signingKeys = config.SigningKeys;
109+
113110
_stsMetadataRetrievalTime = DateTime.UtcNow;
114111
}
115112

116113
issuer = _issuer;
117-
signingTokens = _signingTokens;
114+
signingKeys = _signingKeys;
118115
}
119116
catch (Exception)
120117
{
@@ -129,9 +126,8 @@ protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
129126
ValidAudiences = new[] { audience, clientId },
130127

131128
// Supports both the Azure AD V1 and V2 endpoint
132-
ValidIssuers = new [] { issuer, $"{issuer}/v2.0" },
133-
IssuerSigningTokens = signingTokens,
134-
CertificateValidator = X509CertificateValidator.None // Certificate validation does not make sense since AAD's metadata document is signed with a self-signed certificate.
129+
ValidIssuers = new[] { issuer, $"{issuer}/v2.0" },
130+
IssuerSigningKeys = signingKeys
135131
};
136132

137133
try
@@ -152,15 +148,15 @@ protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
152148
// If the token is scoped, verify that required permission is set in the scope claim.
153149
if (ClaimsPrincipal.Current.FindFirst(scopeClaimType) != null && ClaimsPrincipal.Current.FindFirst(scopeClaimType).Value != "user_impersonation")
154150
{
155-
HttpResponseMessage response = BuildResponseErrorMessage(HttpStatusCode.Forbidden);
151+
HttpResponseMessage response = this.BuildResponseErrorMessage(HttpStatusCode.Forbidden);
156152
return response;
157153
}
158154

159155
return await base.SendAsync(request, cancellationToken);
160156
}
161157
catch (SecurityTokenValidationException)
162158
{
163-
HttpResponseMessage response = BuildResponseErrorMessage(HttpStatusCode.Unauthorized);
159+
HttpResponseMessage response = this.BuildResponseErrorMessage(HttpStatusCode.Unauthorized);
164160
return response;
165161
}
166162
catch (Exception)
@@ -176,11 +172,11 @@ private HttpResponseMessage BuildResponseErrorMessage(HttpStatusCode statusCode)
176172
//
177173
// The Scheme should be "Bearer", authorization_uri should point to the tenant url and resource_id should point to the audience.
178174
//
179-
AuthenticationHeaderValue authenticateHeader = new AuthenticationHeaderValue("Bearer", "authorization_uri=\"" + authority + "\"" + "," + "resource_id=" + audience);
175+
AuthenticationHeaderValue authenticateHeader = new AuthenticationHeaderValue("Bearer", "authorization_uri=\"" + this.authority + "\"" + "," + "resource_id=" + audience);
180176

181177
response.Headers.WwwAuthenticate.Add(authenticateHeader);
182178

183179
return response;
184180
}
185181
}
186-
}
182+
}

0 commit comments

Comments
 (0)