Skip to content

Commit 7e5ca05

Browse files
brentschmaltzjmprieur
authored andcommitted
create ConfigurationManager for instance, use internal refresh (#35)
create TokenHandler for instance remove unused code use current code styling
1 parent cfd4d3a commit 7e5ca05

File tree

2 files changed

+53
-82
lines changed

2 files changed

+53
-82
lines changed

NuGet.Config

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<packageSources>
4+
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
5+
</packageSources>
6+
</configuration>

TodoListService-ManualJwt/Global.asax.cs

+47-82
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,11 @@
1414
// limitations under the License.
1515
//----------------------------------------------------------------------------------------------
1616

17-
using Microsoft.IdentityModel.Protocols;
18-
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
19-
using Microsoft.IdentityModel.Tokens;
2017
using System;
21-
using System.Collections.Generic;
2218
using System.Configuration;
2319
using System.Globalization;
2420
using System.IdentityModel.Tokens.Jwt;
2521
using System.Net;
26-
27-
// The following using statements were added for this sample.
2822
using System.Net.Http;
2923
using System.Net.Http.Headers;
3024
using System.Security.Claims;
@@ -35,6 +29,9 @@
3529
using System.Web.Mvc;
3630
using System.Web.Optimization;
3731
using System.Web.Routing;
32+
using Microsoft.IdentityModel.Protocols;
33+
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
34+
using Microsoft.IdentityModel.Tokens;
3835

3936
namespace TodoListService_ManualJwt
4037
{
@@ -57,126 +54,94 @@ internal class TokenValidationHandler : DelegatingHandler
5754
// The AAD Instance is the instance of Azure, for example public Azure or Azure China.
5855
// The Tenant is the name of the tenant in which this application is registered.
5956
// The Authority is the sign-in URL of the tenant.
60-
// The Audience is the value the service expects to see in tokens that are addressed to it.
57+
// The Audience is the value of one of the 'aud' claims the service expects to find in token to assure the token is addressed to it.
6158
//
62-
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
6359

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-
74-
//
75-
// SendAsync checks that incoming requests have a valid access token, and sets the current user identity using that access token.
76-
//
60+
private string _audience;
61+
private string _authority;
62+
private string _clientId;
63+
private ConfigurationManager<OpenIdConnectConfiguration> _configManager;
64+
private const string _scopeClaimType = "http://schemas.microsoft.com/identity/claims/scope";
65+
private ISecurityTokenValidator _tokenValidator;
66+
67+
public TokenValidationHandler()
68+
{
69+
_audience = ConfigurationManager.AppSettings["ida:Audience"];
70+
_clientId = ConfigurationManager.AppSettings["ida:ClientId"];
71+
var aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
72+
var tenant = ConfigurationManager.AppSettings["ida:Tenant"];
73+
_authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
74+
_configManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{_authority}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
75+
_tokenValidator = new JwtSecurityTokenHandler();
76+
}
77+
78+
/// <summary>
79+
/// Checks that incoming requests have a valid access token, and sets the current user identity using that access token.
80+
/// </summary>
81+
/// <param name="request">the current <see cref="HttpRequestMessage"/>.</param>
82+
/// <param name="cancellationToken">a <see cref="CancellationToken"/> set by application.</param>
83+
/// <returns>A <see cref="HttpResponseMessage"/>.</returns>
7784
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
7885
{
79-
// Get the jwt bearer token from the authorization header
80-
string jwtToken = null;
81-
AuthenticationHeaderValue authHeader = request.Headers.Authorization;
82-
if (authHeader != null)
83-
{
84-
jwtToken = authHeader.Parameter;
85-
}
86-
87-
if (jwtToken == null)
88-
{
89-
HttpResponseMessage response = this.BuildResponseErrorMessage(HttpStatusCode.Unauthorized);
90-
return response;
91-
}
92-
93-
string issuer;
94-
ICollection<SecurityKey> signingKeys;
86+
// check there is a jwt in the authorization header, return 'Unauthorized' error if the token is null.
87+
if (request.Headers.Authorization == null || request.Headers.Authorization.Parameter == null)
88+
return BuildResponseErrorMessage(HttpStatusCode.Unauthorized);
9589

90+
OpenIdConnectConfiguration config = null;
9691
try
9792
{
98-
// The issuer and signingKeys are cached for 24 hours. They are updated if any of the conditions in the if condition is true.
99-
if (DateTime.UtcNow.Subtract(_stsMetadataRetrievalTime).TotalHours > 24
100-
|| string.IsNullOrEmpty(_issuer)
101-
|| _signingKeys == null)
102-
{
103-
// Get tenant information that's used to validate incoming jwt tokens
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);
107-
_issuer = config.Issuer;
108-
_signingKeys = config.SigningKeys;
109-
110-
_stsMetadataRetrievalTime = DateTime.UtcNow;
111-
}
112-
113-
issuer = _issuer;
114-
signingKeys = _signingKeys;
93+
config = await _configManager.GetConfigurationAsync(cancellationToken).ConfigureAwait(false);
11594
}
11695
catch (Exception)
11796
{
11897
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
11998
}
12099

121-
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
122-
123100
TokenValidationParameters validationParameters = new TokenValidationParameters
124101
{
125-
// We accept both the App Id URI and the AppId of this service application
126-
ValidAudiences = new[] { audience, clientId },
127-
128-
// Supports both the Azure AD V1 and V2 endpoint
129-
ValidIssuers = new[] { issuer, $"{issuer}/v2.0" },
130-
IssuerSigningKeys = signingKeys
102+
// App Id URI and AppId of this service application are both valid audiences.
103+
ValidAudiences = new[] { _audience, _clientId },
104+
// Support Azure AD V1 and V2 endpoints.
105+
ValidIssuers = new[] { config.Issuer, $"{config.Issuer}/v2.0" },
106+
IssuerSigningKeys = config.SigningKeys
131107
};
132108

133109
try
134-
{
110+
{
135111
// Validate token.
136-
SecurityToken validatedToken = new JwtSecurityToken();
137-
ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters, out validatedToken);
112+
var claimsPrincipal = _tokenValidator.ValidateToken(request.Headers.Authorization.Parameter, validationParameters, out SecurityToken _);
138113

139114
// Set the ClaimsPrincipal on the current thread.
140115
Thread.CurrentPrincipal = claimsPrincipal;
141116

142117
// Set the ClaimsPrincipal on HttpContext.Current if the app is running in web hosted environment.
143118
if (HttpContext.Current != null)
144-
{
145119
HttpContext.Current.User = claimsPrincipal;
146-
}
147120

148121
// If the token is scoped, verify that required permission is set in the scope claim.
149-
if (ClaimsPrincipal.Current.FindFirst(scopeClaimType) != null && ClaimsPrincipal.Current.FindFirst(scopeClaimType).Value != "user_impersonation")
150-
{
151-
HttpResponseMessage response = this.BuildResponseErrorMessage(HttpStatusCode.Forbidden);
152-
return response;
153-
}
122+
if (ClaimsPrincipal.Current.FindFirst(_scopeClaimType) != null && ClaimsPrincipal.Current.FindFirst(_scopeClaimType).Value != "user_impersonation")
123+
return BuildResponseErrorMessage(HttpStatusCode.Forbidden);
154124

155125
return await base.SendAsync(request, cancellationToken);
156126
}
157127
catch (SecurityTokenValidationException)
158128
{
159-
HttpResponseMessage response = this.BuildResponseErrorMessage(HttpStatusCode.Unauthorized);
160-
return response;
129+
return BuildResponseErrorMessage(HttpStatusCode.Unauthorized);
161130
}
162131
catch (Exception)
163132
{
164133
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
165134
}
166-
}
135+
}
167136

168137
private HttpResponseMessage BuildResponseErrorMessage(HttpStatusCode statusCode)
169138
{
170-
HttpResponseMessage response = new HttpResponseMessage(statusCode);
139+
var response = new HttpResponseMessage(statusCode);
171140

172-
//
173141
// The Scheme should be "Bearer", authorization_uri should point to the tenant url and resource_id should point to the audience.
174-
//
175-
AuthenticationHeaderValue authenticateHeader = new AuthenticationHeaderValue("Bearer", "authorization_uri=\"" + this.authority + "\"" + "," + "resource_id=" + audience);
176-
142+
var authenticateHeader = new AuthenticationHeaderValue("Bearer", "authorization_uri=\"" + _authority + "\"" + "," + "resource_id=" + _audience);
177143
response.Headers.WwwAuthenticate.Add(authenticateHeader);
178-
179144
return response;
180145
}
181146
}
182-
}
147+
}

0 commit comments

Comments
 (0)