@@ -96,79 +96,82 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
9696 }
9797 }
9898
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-
99+ var tvp = await SetupTokenValidationParameters ( ) ;
114100 List < Exception > ? validationFailures = null ;
115101 SecurityToken ? validatedToken = null ;
116- foreach ( var validator in Options . SecurityTokenValidators )
102+ ClaimsPrincipal ? principal = null ;
103+
104+ if ( Options . UseTokenHandlers )
117105 {
118- if ( validator . CanReadToken ( token ) )
106+ foreach ( var tokenHandler in Options . TokenHandlers )
119107 {
120- ClaimsPrincipal principal ;
121- try
108+ var tokenValidationResult = await tokenHandler . ValidateTokenAsync ( token , tvp ) ;
109+ if ( tokenValidationResult . IsValid )
122110 {
123- principal = validator . ValidateToken ( token , validationParameters , out validatedToken ) ;
111+ principal = new ClaimsPrincipal ( tokenValidationResult . ClaimsIdentity ) ;
112+ validatedToken = tokenValidationResult . SecurityToken ;
113+ break ;
124114 }
125- catch ( Exception ex )
115+ else
126116 {
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 )
117+ // TODO - what to log if there is no exception.
118+ if ( tokenValidationResult . Exception != null )
132119 {
133- Options . ConfigurationManager . RequestRefresh ( ) ;
120+ validationFailures ??= new List < Exception > ( 1 ) ;
121+ RecordTokenValidationError ( tokenValidationResult . Exception , validationFailures ) ;
134122 }
135-
136- if ( validationFailures == null )
123+ }
124+ }
125+ }
126+ else
127+ {
128+ foreach ( var validator in Options . SecurityTokenValidators )
129+ {
130+ if ( validator . CanReadToken ( token ) )
131+ {
132+ try
133+ {
134+ principal = validator . ValidateToken ( token , tvp , out validatedToken ) ;
135+ }
136+ catch ( Exception ex )
137137 {
138- validationFailures = new List < Exception > ( 1 ) ;
138+ validationFailures ??= new List < Exception > ( 1 ) ;
139+ RecordTokenValidationError ( ex , validationFailures ) ;
140+ continue ;
139141 }
140- validationFailures . Add ( ex ) ;
141- continue ;
142142 }
143+ }
144+ }
143145
144- Logger . TokenValidationSucceeded ( ) ;
146+ if ( principal != null && validatedToken != null )
147+ {
148+ Logger . TokenValidationSucceeded ( ) ;
145149
146- var tokenValidatedContext = new TokenValidatedContext ( Context , Scheme , Options )
147- {
148- Principal = principal ,
149- SecurityToken = validatedToken
150- } ;
150+ var tokenValidatedContext = new TokenValidatedContext ( Context , Scheme , Options )
151+ {
152+ Principal = principal
153+ } ;
151154
152- tokenValidatedContext . Properties . ExpiresUtc = GetSafeDateTime ( validatedToken . ValidTo ) ;
153- tokenValidatedContext . Properties . IssuedUtc = GetSafeDateTime ( validatedToken . ValidFrom ) ;
155+ tokenValidatedContext . SecurityToken = validatedToken ;
156+ tokenValidatedContext . Properties . ExpiresUtc = GetSafeDateTime ( validatedToken . ValidTo ) ;
157+ tokenValidatedContext . Properties . IssuedUtc = GetSafeDateTime ( validatedToken . ValidFrom ) ;
154158
155- await Events . TokenValidated ( tokenValidatedContext ) ;
156- if ( tokenValidatedContext . Result != null )
157- {
158- return tokenValidatedContext . Result ;
159- }
159+ await Events . TokenValidated ( tokenValidatedContext ) ;
160+ if ( tokenValidatedContext . Result != null )
161+ {
162+ return tokenValidatedContext . Result ;
163+ }
160164
161- if ( Options . SaveToken )
165+ if ( Options . SaveToken )
166+ {
167+ tokenValidatedContext . Properties . StoreTokens ( new [ ]
162168 {
163- tokenValidatedContext . Properties . StoreTokens ( new [ ]
164- {
165- new AuthenticationToken { Name = "access_token" , Value = token }
166- } ) ;
167- }
168-
169- tokenValidatedContext . Success ( ) ;
170- return tokenValidatedContext . Result ! ;
169+ new AuthenticationToken { Name = "access_token" , Value = token }
170+ } ) ;
171171 }
172+
173+ tokenValidatedContext . Success ( ) ;
174+ return tokenValidatedContext . Result ! ;
172175 }
173176
174177 if ( validationFailures != null )
@@ -187,6 +190,11 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
187190 return AuthenticateResult . Fail ( authenticationFailedContext . Exception ) ;
188191 }
189192
193+ if ( Options . UseTokenHandlers )
194+ {
195+ return AuthenticateResults . TokenHandlerUnableToValidate ;
196+ }
197+
190198 return AuthenticateResults . ValidatorNotFound ;
191199 }
192200 catch ( Exception ex )
@@ -208,6 +216,62 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
208216 }
209217 }
210218
219+ // TODO - this method could be shared across OIDC, WsFed, JwtBearer to ensure consistency
220+ private void RecordTokenValidationError ( Exception exception , List < Exception > exceptions )
221+ {
222+ if ( exception != null )
223+ {
224+ Logger . TokenValidationFailed ( exception ) ;
225+ exceptions . Add ( exception ) ;
226+ }
227+
228+ // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
229+ // Refreshing on SecurityTokenSignatureKeyNotFound may be redundant if Last-Known-Good is enabled, it won't do much harm, most likely will be a nop.
230+
231+ if ( Options . RefreshOnIssuerKeyNotFound && Options . ConfigurationManager != null
232+ && exception is SecurityTokenSignatureKeyNotFoundException )
233+ {
234+ Options . ConfigurationManager . RequestRefresh ( ) ;
235+ }
236+ }
237+
238+ // TODO - this method could be shared across OIDC, WsFed, JwtBearer to ensure consistency
239+ private async Task < TokenValidationParameters > SetupTokenValidationParameters ( )
240+ {
241+ // Clone to avoid cross request race conditions for updated configurations.
242+ var tokenValidationParameters = Options . TokenValidationParameters . Clone ( ) ;
243+
244+ if ( Options . ConfigurationManager is BaseConfigurationManager baseConfigurationManager )
245+ {
246+ // TODO - we need to add a parameter to TokenValidationParameters for the CancellationToken.
247+ tokenValidationParameters . ConfigurationManager = baseConfigurationManager ;
248+ }
249+ else
250+ {
251+ if ( Options . ConfigurationManager != null )
252+ {
253+
254+ // TODO - understand existing code:
255+ // ConfigurationManager has a refresh interval of 12 hours by default and will only make an https call every 12 hours.
256+ // Not sure where it would ever get updated before
257+
258+ // if (_configuration == null && Options.ConfigurationManager != null)
259+ // {
260+ // _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
261+ // }
262+
263+ // it is safe to just call GetConfigurationAsync
264+ _configuration = await Options . ConfigurationManager . GetConfigurationAsync ( Context . RequestAborted ) ;
265+
266+ var issuers = new [ ] { _configuration . Issuer } ;
267+ tokenValidationParameters . ValidIssuers = ( tokenValidationParameters . ValidIssuers == null ? issuers : tokenValidationParameters . ValidIssuers . Concat ( issuers ) ) ;
268+ tokenValidationParameters . IssuerSigningKeys = ( tokenValidationParameters . IssuerSigningKeys == null ? _configuration . SigningKeys : tokenValidationParameters . IssuerSigningKeys . Concat ( _configuration . SigningKeys ) ) ;
269+ }
270+ }
271+
272+ return tokenValidationParameters ;
273+ }
274+
211275 private static DateTime ? GetSafeDateTime ( DateTime dateTime )
212276 {
213277 // Assigning DateTime.MinValue or default(DateTime) to a DateTimeOffset when in a UTC+X timezone will throw
0 commit comments