1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
+ using System ;
4
5
using System . Globalization ;
5
6
using System . Linq ;
6
7
using System . Security . Claims ;
@@ -96,79 +97,86 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
96
97
}
97
98
}
98
99
99
- if ( _configuration == null && Options . ConfigurationManager != null )
100
- {
101
- _configuration = await Options . ConfigurationManager . GetConfigurationAsync ( Context . RequestAborted ) ;
102
- }
103
-
104
- var validationParameters = Options . TokenValidationParameters . Clone ( ) ;
105
- if ( _configuration != null )
106
- {
107
- var issuers = new [ ] { _configuration . Issuer } ;
108
- validationParameters . ValidIssuers = validationParameters . ValidIssuers ? . Concat ( issuers ) ?? issuers ;
109
-
110
- validationParameters . IssuerSigningKeys = validationParameters . IssuerSigningKeys ? . Concat ( _configuration . SigningKeys )
111
- ?? _configuration . SigningKeys ;
112
- }
113
-
100
+ var tvp = await SetupTokenValidationParameters ( ) ;
114
101
List < Exception > ? validationFailures = null ;
115
102
SecurityToken ? validatedToken = null ;
116
- foreach ( var validator in Options . SecurityTokenValidators )
103
+ ClaimsPrincipal ? principal = null ;
104
+
105
+ if ( Options . UseTokenHandlers )
117
106
{
118
- if ( validator . CanReadToken ( token ) )
107
+ foreach ( var tokenHandler in Options . TokenHandlers )
119
108
{
120
- ClaimsPrincipal principal ;
121
109
try
122
110
{
123
- principal = validator . ValidateToken ( token , validationParameters , out validatedToken ) ;
111
+ var tokenValidationResult = await tokenHandler . ValidateTokenAsync ( token , tvp ) ;
112
+ if ( tokenValidationResult . IsValid )
113
+ {
114
+ principal = new ClaimsPrincipal ( tokenValidationResult . ClaimsIdentity ) ;
115
+ validatedToken = tokenValidationResult . SecurityToken ;
116
+ break ;
117
+ }
118
+ else
119
+ {
120
+ validationFailures ??= new List < Exception > ( 1 ) ;
121
+ RecordTokenValidationError ( tokenValidationResult . Exception ?? new SecurityTokenValidationException ( $ "The TokenHandler: '{ tokenHandler } ', was unable to validate the Token.") , validationFailures ) ;
122
+ }
124
123
}
125
124
catch ( Exception ex )
126
125
{
127
- Logger . TokenValidationFailed ( ex ) ;
128
-
129
- // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
130
- if ( Options . RefreshOnIssuerKeyNotFound && Options . ConfigurationManager != null
131
- && ex is SecurityTokenSignatureKeyNotFoundException )
126
+ validationFailures ??= new List < Exception > ( 1 ) ;
127
+ RecordTokenValidationError ( new SecurityTokenValidationException ( $ "TokenHandler: '{ tokenHandler } ', threw an exception (see inner exception).", ex ) , validationFailures ) ;
128
+ }
129
+ }
130
+ }
131
+ else
132
+ {
133
+ foreach ( var validator in Options . SecurityTokenValidators )
134
+ {
135
+ if ( validator . CanReadToken ( token ) )
136
+ {
137
+ try
132
138
{
133
- Options . ConfigurationManager . RequestRefresh ( ) ;
139
+ principal = validator . ValidateToken ( token , tvp , out validatedToken ) ;
134
140
}
135
-
136
- if ( validationFailures == null )
141
+ catch ( Exception ex )
137
142
{
138
- validationFailures = new List < Exception > ( 1 ) ;
143
+ validationFailures ??= new List < Exception > ( 1 ) ;
144
+ RecordTokenValidationError ( ex , validationFailures ) ;
145
+ continue ;
139
146
}
140
- validationFailures . Add ( ex ) ;
141
- continue ;
142
147
}
148
+ }
149
+ }
143
150
144
- Logger . TokenValidationSucceeded ( ) ;
151
+ if ( principal != null && validatedToken != null )
152
+ {
153
+ Logger . TokenValidationSucceeded ( ) ;
145
154
146
- var tokenValidatedContext = new TokenValidatedContext ( Context , Scheme , Options )
147
- {
148
- Principal = principal ,
149
- SecurityToken = validatedToken
150
- } ;
155
+ var tokenValidatedContext = new TokenValidatedContext ( Context , Scheme , Options )
156
+ {
157
+ Principal = principal
158
+ } ;
151
159
152
- tokenValidatedContext . Properties . ExpiresUtc = GetSafeDateTime ( validatedToken . ValidTo ) ;
153
- tokenValidatedContext . Properties . IssuedUtc = GetSafeDateTime ( validatedToken . ValidFrom ) ;
160
+ tokenValidatedContext . SecurityToken = validatedToken ;
161
+ tokenValidatedContext . Properties . ExpiresUtc = GetSafeDateTime ( validatedToken . ValidTo ) ;
162
+ tokenValidatedContext . Properties . IssuedUtc = GetSafeDateTime ( validatedToken . ValidFrom ) ;
154
163
155
- await Events . TokenValidated ( tokenValidatedContext ) ;
156
- if ( tokenValidatedContext . Result != null )
157
- {
158
- return tokenValidatedContext . Result ;
159
- }
164
+ await Events . TokenValidated ( tokenValidatedContext ) ;
165
+ if ( tokenValidatedContext . Result != null )
166
+ {
167
+ return tokenValidatedContext . Result ;
168
+ }
160
169
161
- if ( Options . SaveToken )
170
+ if ( Options . SaveToken )
171
+ {
172
+ tokenValidatedContext . Properties . StoreTokens ( new [ ]
162
173
{
163
- tokenValidatedContext . Properties . StoreTokens ( new [ ]
164
- {
165
- new AuthenticationToken { Name = "access_token" , Value = token }
166
- } ) ;
167
- }
168
-
169
- tokenValidatedContext . Success ( ) ;
170
- return tokenValidatedContext . Result ! ;
174
+ new AuthenticationToken { Name = "access_token" , Value = token }
175
+ } ) ;
171
176
}
177
+
178
+ tokenValidatedContext . Success ( ) ;
179
+ return tokenValidatedContext . Result ! ;
172
180
}
173
181
174
182
if ( validationFailures != null )
@@ -187,6 +195,11 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
187
195
return AuthenticateResult . Fail ( authenticationFailedContext . Exception ) ;
188
196
}
189
197
198
+ if ( Options . UseTokenHandlers )
199
+ {
200
+ return AuthenticateResults . TokenHandlerUnableToValidate ;
201
+ }
202
+
190
203
return AuthenticateResults . ValidatorNotFound ;
191
204
}
192
205
catch ( Exception ex )
@@ -208,6 +221,47 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
208
221
}
209
222
}
210
223
224
+ private void RecordTokenValidationError ( Exception exception , List < Exception > exceptions )
225
+ {
226
+ if ( exception != null )
227
+ {
228
+ Logger . TokenValidationFailed ( exception ) ;
229
+ exceptions . Add ( exception ) ;
230
+ }
231
+
232
+ // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
233
+ // Refreshing on SecurityTokenSignatureKeyNotFound may be redundant if Last-Known-Good is enabled, it won't do much harm, most likely will be a nop.
234
+ if ( Options . RefreshOnIssuerKeyNotFound && Options . ConfigurationManager != null
235
+ && exception is SecurityTokenSignatureKeyNotFoundException )
236
+ {
237
+ Options . ConfigurationManager . RequestRefresh ( ) ;
238
+ }
239
+ }
240
+
241
+ private async Task < TokenValidationParameters > SetupTokenValidationParameters ( )
242
+ {
243
+ // Clone to avoid cross request race conditions for updated configurations.
244
+ var tokenValidationParameters = Options . TokenValidationParameters . Clone ( ) ;
245
+
246
+ if ( Options . ConfigurationManager is BaseConfigurationManager baseConfigurationManager )
247
+ {
248
+ tokenValidationParameters . ConfigurationManager = baseConfigurationManager ;
249
+ }
250
+ else
251
+ {
252
+ if ( Options . ConfigurationManager != null )
253
+ {
254
+ // GetConfigurationAsync has a time interval that must pass before new http request will be issued.
255
+ _configuration = await Options . ConfigurationManager . GetConfigurationAsync ( Context . RequestAborted ) ;
256
+ var issuers = new [ ] { _configuration . Issuer } ;
257
+ tokenValidationParameters . ValidIssuers = ( tokenValidationParameters . ValidIssuers == null ? issuers : tokenValidationParameters . ValidIssuers . Concat ( issuers ) ) ;
258
+ tokenValidationParameters . IssuerSigningKeys = ( tokenValidationParameters . IssuerSigningKeys == null ? _configuration . SigningKeys : tokenValidationParameters . IssuerSigningKeys . Concat ( _configuration . SigningKeys ) ) ;
259
+ }
260
+ }
261
+
262
+ return tokenValidationParameters ;
263
+ }
264
+
211
265
private static DateTime ? GetSafeDateTime ( DateTime dateTime )
212
266
{
213
267
// Assigning DateTime.MinValue or default(DateTime) to a DateTimeOffset when in a UTC+X timezone will throw
0 commit comments