14
14
// limitations under the License.
15
15
//----------------------------------------------------------------------------------------------
16
16
17
- using Microsoft . IdentityModel . Protocols ;
18
- using Microsoft . IdentityModel . Protocols . OpenIdConnect ;
19
- using Microsoft . IdentityModel . Tokens ;
20
17
using System ;
21
- using System . Collections . Generic ;
22
18
using System . Configuration ;
23
19
using System . Globalization ;
24
20
using System . IdentityModel . Tokens . Jwt ;
25
21
using System . Net ;
26
-
27
- // The following using statements were added for this sample.
28
22
using System . Net . Http ;
29
23
using System . Net . Http . Headers ;
30
24
using System . Security . Claims ;
35
29
using System . Web . Mvc ;
36
30
using System . Web . Optimization ;
37
31
using System . Web . Routing ;
32
+ using Microsoft . IdentityModel . Protocols ;
33
+ using Microsoft . IdentityModel . Protocols . OpenIdConnect ;
34
+ using Microsoft . IdentityModel . Tokens ;
38
35
39
36
namespace TodoListService_ManualJwt
40
37
{
@@ -57,126 +54,94 @@ internal class TokenValidationHandler : DelegatingHandler
57
54
// The AAD Instance is the instance of Azure, for example public Azure or Azure China.
58
55
// The Tenant is the name of the tenant in which this application is registered.
59
56
// 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.
61
58
//
62
- private static string aadInstance = ConfigurationManager . AppSettings [ "ida:AADInstance" ] ;
63
59
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>
77
84
protected async override Task < HttpResponseMessage > SendAsync ( HttpRequestMessage request , CancellationToken cancellationToken )
78
85
{
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 ) ;
95
89
90
+ OpenIdConnectConfiguration config = null ;
96
91
try
97
92
{
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 ) ;
115
94
}
116
95
catch ( Exception )
117
96
{
118
97
return new HttpResponseMessage ( HttpStatusCode . InternalServerError ) ;
119
98
}
120
99
121
- JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler ( ) ;
122
-
123
100
TokenValidationParameters validationParameters = new TokenValidationParameters
124
101
{
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
131
107
} ;
132
108
133
109
try
134
- {
110
+ {
135
111
// 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 _ ) ;
138
113
139
114
// Set the ClaimsPrincipal on the current thread.
140
115
Thread . CurrentPrincipal = claimsPrincipal ;
141
116
142
117
// Set the ClaimsPrincipal on HttpContext.Current if the app is running in web hosted environment.
143
118
if ( HttpContext . Current != null )
144
- {
145
119
HttpContext . Current . User = claimsPrincipal ;
146
- }
147
120
148
121
// 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 ) ;
154
124
155
125
return await base . SendAsync ( request , cancellationToken ) ;
156
126
}
157
127
catch ( SecurityTokenValidationException )
158
128
{
159
- HttpResponseMessage response = this . BuildResponseErrorMessage ( HttpStatusCode . Unauthorized ) ;
160
- return response ;
129
+ return BuildResponseErrorMessage ( HttpStatusCode . Unauthorized ) ;
161
130
}
162
131
catch ( Exception )
163
132
{
164
133
return new HttpResponseMessage ( HttpStatusCode . InternalServerError ) ;
165
134
}
166
- }
135
+ }
167
136
168
137
private HttpResponseMessage BuildResponseErrorMessage ( HttpStatusCode statusCode )
169
138
{
170
- HttpResponseMessage response = new HttpResponseMessage ( statusCode ) ;
139
+ var response = new HttpResponseMessage ( statusCode ) ;
171
140
172
- //
173
141
// 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 ) ;
177
143
response . Headers . WwwAuthenticate . Add ( authenticateHeader ) ;
178
-
179
144
return response ;
180
145
}
181
146
}
182
- }
147
+ }
0 commit comments