Skip to content

Commit 2922b05

Browse files
keegan-carusoKeegan CarusoBrent Schmaltzbrentschmaltzcaptainsafia
authored
Update JwtBearer, WsFed, and OIDC handlers to use identity model 7 (#49542)
* Option to use JsonWebTokenHandler in OpenIdConnectHandler * fix sample * split event tests * update IdentityModel to 6.31.0 * Added JsonWebTokenHandler and TokenHandlers (#48857) Co-authored-by: Brent Schmaltz <[email protected]> * adjust for claims mapping remove using System * moved api's to unshipped * Addressed PR comments * Removed setter. * Use 7.0.0-preview for identity model libraries * fix bild break, useTokenHanlders default false. * use var for identitymodel versions * Move new apis to unshipped * Increase key size to 256 bits or HMAC will fail. * update version of identity model libraries (#49349) Co-authored-by: Keegan Caruso <[email protected]> * Update Wilson7 branch (#49491) * SymmetricSecurityKey needs 32 bytes * Update source-build-externals dependencies --------- Co-authored-by: Keegan Caruso <[email protected]> * Update Wilson7 branch (#49524) * Update Wilson7 branch Default to using new handlers Changes from API review * Handle obsolete members * Handle obsolete members in tests * Add aka.ms link --------- Co-authored-by: Keegan Caruso <[email protected]> * Changes from API review * Comments from review --------- Co-authored-by: Keegan Caruso <[email protected]> Co-authored-by: Brent Schmaltz <[email protected]> Co-authored-by: BrentSchmaltz <[email protected]> Co-authored-by: Safia Abdalla <[email protected]>
1 parent 11a3b5e commit 2922b05

29 files changed

+3715
-114
lines changed

eng/Version.Details.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,9 @@
185185
<Uri>https://github.com/dotnet/runtime</Uri>
186186
<Sha>ebd23467f0b677c66ad85cf4e63ffef4a50acc25</Sha>
187187
</Dependency>
188-
<Dependency Name="Microsoft.SourceBuild.Intermediate.source-build-externals" Version="8.0.0-alpha.1.23362.1">
188+
<Dependency Name="Microsoft.SourceBuild.Intermediate.source-build-externals" Version="8.0.0-alpha.1.23368.1">
189189
<Uri>https://github.com/dotnet/source-build-externals</Uri>
190-
<Sha>76026f9224bd83ede7b2f494912694a30169c233</Sha>
190+
<Sha>844e2cd86e7525d7eb32358e63a0c554187eb26b</Sha>
191191
<SourceBuild RepoName="source-build-externals" ManagedOnly="true" />
192192
</Dependency>
193193
<Dependency Name="Microsoft.SourceBuild.Intermediate.symreader" Version="2.0.0-beta-23228-03">

eng/Versions.props

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<AspNetCorePatchVersion>0</AspNetCorePatchVersion>
1212
<PreReleaseVersionIteration>7</PreReleaseVersionIteration>
1313
<ValidateBaseline>true</ValidateBaseline>
14+
<IdentityModelVersion>7.0.0-preview</IdentityModelVersion>
1415
<!--
1516
When StabilizePackageVersion is set to 'true', this branch will produce stable outputs for 'Shipping' packages
1617
-->
@@ -162,7 +163,7 @@
162163
<!-- Packages from dotnet/source-link -->
163164
<MicrosoftSourceLinkGitHubVersion>8.0.0-beta.23361.2</MicrosoftSourceLinkGitHubVersion>
164165
<!-- Packages from dotnet/source-build-externals -->
165-
<MicrosoftSourceBuildIntermediatesourcebuildexternalsVersion>8.0.0-alpha.1.23362.1</MicrosoftSourceBuildIntermediatesourcebuildexternalsVersion>
166+
<MicrosoftSourceBuildIntermediatesourcebuildexternalsVersion>8.0.0-alpha.1.23368.1</MicrosoftSourceBuildIntermediatesourcebuildexternalsVersion>
166167
<!-- Packages from dotnet/source-build-reference-packages -->
167168
<MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesVersion>8.0.0-alpha.1.23362.3</MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesVersion>
168169
<!-- Packages from dotnet/symreader -->
@@ -254,15 +255,15 @@
254255
<MicrosoftCodeAnalysisCSharpAnalyzerTestingXUnitVersion>1.1.2-beta1.22531.1</MicrosoftCodeAnalysisCSharpAnalyzerTestingXUnitVersion>
255256
<MicrosoftCodeAnalysisCSharpCodeFixTestingXUnitVersion>1.1.2-beta1.22531.1</MicrosoftCodeAnalysisCSharpCodeFixTestingXUnitVersion>
256257
<MicrosoftCssParserVersion>1.0.0-20230414.1</MicrosoftCssParserVersion>
257-
<MicrosoftIdentityModelLoggingVersion>6.15.1</MicrosoftIdentityModelLoggingVersion>
258-
<MicrosoftIdentityModelProtocolsOpenIdConnectVersion>6.15.1</MicrosoftIdentityModelProtocolsOpenIdConnectVersion>
259-
<MicrosoftIdentityModelProtocolsWsFederationVersion>6.15.1</MicrosoftIdentityModelProtocolsWsFederationVersion>
258+
<MicrosoftIdentityModelLoggingVersion>$(IdentityModelVersion)</MicrosoftIdentityModelLoggingVersion>
259+
<MicrosoftIdentityModelProtocolsOpenIdConnectVersion>$(IdentityModelVersion)</MicrosoftIdentityModelProtocolsOpenIdConnectVersion>
260+
<MicrosoftIdentityModelProtocolsWsFederationVersion>$(IdentityModelVersion)</MicrosoftIdentityModelProtocolsWsFederationVersion>
260261
<MicrosoftInternalAspNetCoreH2SpecAllVersion>2.2.1</MicrosoftInternalAspNetCoreH2SpecAllVersion>
261262
<MicrosoftNETCoreWindowsApiSetsVersion>1.0.1</MicrosoftNETCoreWindowsApiSetsVersion>
262263
<MicrosoftOwinSecurityCookiesVersion>3.0.1</MicrosoftOwinSecurityCookiesVersion>
263264
<MicrosoftOwinTestingVersion>3.0.1</MicrosoftOwinTestingVersion>
264265
<MicrosoftWebAdministrationVersion>11.1.0</MicrosoftWebAdministrationVersion>
265-
<SystemIdentityModelTokensJwtVersion>6.21.0</SystemIdentityModelTokensJwtVersion>
266+
<SystemIdentityModelTokensJwtVersion>$(IdentityModelVersion)</SystemIdentityModelTokensJwtVersion>
266267
<SystemComponentModelAnnotationsVersion>5.0.0</SystemComponentModelAnnotationsVersion>
267268
<SystemNetExperimentalMsQuicVersion>5.0.0-alpha.20560.6</SystemNetExperimentalMsQuicVersion>
268269
<SystemSecurityPrincipalWindowsVersion>5.0.0</SystemSecurityPrincipalWindowsVersion>

src/Security/Authentication/JwtBearer/src/AuthenticateResults.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer;
66
internal static class AuthenticateResults
77
{
88
internal static AuthenticateResult ValidatorNotFound = AuthenticateResult.Fail("No SecurityTokenValidator available for token.");
9+
internal static AuthenticateResult TokenHandlerUnableToValidate = AuthenticateResult.Fail("No TokenHandler was able to validate the token.");
910
}

src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs

Lines changed: 107 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using Microsoft.AspNetCore.Http;
1010
using Microsoft.Extensions.Logging;
1111
using Microsoft.Extensions.Options;
12-
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
1312
using Microsoft.IdentityModel.Tokens;
1413
using Microsoft.Net.Http.Headers;
1514

@@ -20,8 +19,6 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer;
2019
/// </summary>
2120
public class JwtBearerHandler : AuthenticationHandler<JwtBearerOptions>
2221
{
23-
private OpenIdConnectConfiguration? _configuration;
24-
2522
/// <summary>
2623
/// Initializes a new instance of <see cref="JwtBearerHandler"/>.
2724
/// </summary>
@@ -96,79 +93,88 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
9693
}
9794
}
9895

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-
96+
var tvp = await SetupTokenValidationParametersAsync();
11497
List<Exception>? validationFailures = null;
11598
SecurityToken? validatedToken = null;
116-
foreach (var validator in Options.SecurityTokenValidators)
99+
ClaimsPrincipal? principal = null;
100+
101+
if (!Options.UseSecurityTokenValidators)
117102
{
118-
if (validator.CanReadToken(token))
103+
foreach (var tokenHandler in Options.TokenHandlers)
119104
{
120-
ClaimsPrincipal principal;
121105
try
122106
{
123-
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
107+
var tokenValidationResult = await tokenHandler.ValidateTokenAsync(token, tvp);
108+
if (tokenValidationResult.IsValid)
109+
{
110+
principal = new ClaimsPrincipal(tokenValidationResult.ClaimsIdentity);
111+
validatedToken = tokenValidationResult.SecurityToken;
112+
break;
113+
}
114+
else
115+
{
116+
validationFailures ??= new List<Exception>(1);
117+
RecordTokenValidationError(tokenValidationResult.Exception ?? new SecurityTokenValidationException($"The TokenHandler: '{tokenHandler}', was unable to validate the Token."), validationFailures);
118+
}
124119
}
125120
catch (Exception ex)
126121
{
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)
122+
validationFailures ??= new List<Exception>(1);
123+
RecordTokenValidationError(ex, validationFailures);
124+
}
125+
}
126+
}
127+
else
128+
{
129+
#pragma warning disable CS0618 // Type or member is obsolete
130+
foreach (var validator in Options.SecurityTokenValidators)
131+
{
132+
if (validator.CanReadToken(token))
133+
{
134+
try
132135
{
133-
Options.ConfigurationManager.RequestRefresh();
136+
principal = validator.ValidateToken(token, tvp, out validatedToken);
134137
}
135-
136-
if (validationFailures == null)
138+
catch (Exception ex)
137139
{
138-
validationFailures = new List<Exception>(1);
140+
validationFailures ??= new List<Exception>(1);
141+
RecordTokenValidationError(ex, validationFailures);
142+
continue;
139143
}
140-
validationFailures.Add(ex);
141-
continue;
142144
}
145+
}
146+
#pragma warning restore CS0618 // Type or member is obsolete
147+
}
143148

144-
Logger.TokenValidationSucceeded();
149+
if (principal != null && validatedToken != null)
150+
{
151+
Logger.TokenValidationSucceeded();
145152

146-
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
147-
{
148-
Principal = principal,
149-
SecurityToken = validatedToken
150-
};
153+
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
154+
{
155+
Principal = principal
156+
};
151157

152-
tokenValidatedContext.Properties.ExpiresUtc = GetSafeDateTime(validatedToken.ValidTo);
153-
tokenValidatedContext.Properties.IssuedUtc = GetSafeDateTime(validatedToken.ValidFrom);
158+
tokenValidatedContext.SecurityToken = validatedToken;
159+
tokenValidatedContext.Properties.ExpiresUtc = GetSafeDateTime(validatedToken.ValidTo);
160+
tokenValidatedContext.Properties.IssuedUtc = GetSafeDateTime(validatedToken.ValidFrom);
154161

155-
await Events.TokenValidated(tokenValidatedContext);
156-
if (tokenValidatedContext.Result != null)
157-
{
158-
return tokenValidatedContext.Result;
159-
}
162+
await Events.TokenValidated(tokenValidatedContext);
163+
if (tokenValidatedContext.Result != null)
164+
{
165+
return tokenValidatedContext.Result;
166+
}
160167

161-
if (Options.SaveToken)
168+
if (Options.SaveToken)
169+
{
170+
tokenValidatedContext.Properties.StoreTokens(new[]
162171
{
163-
tokenValidatedContext.Properties.StoreTokens(new[]
164-
{
165-
new AuthenticationToken { Name = "access_token", Value = token }
166-
});
167-
}
168-
169-
tokenValidatedContext.Success();
170-
return tokenValidatedContext.Result!;
172+
new AuthenticationToken { Name = "access_token", Value = token }
173+
});
171174
}
175+
176+
tokenValidatedContext.Success();
177+
return tokenValidatedContext.Result!;
172178
}
173179

174180
if (validationFailures != null)
@@ -187,6 +193,11 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
187193
return AuthenticateResult.Fail(authenticationFailedContext.Exception);
188194
}
189195

196+
if (!Options.UseSecurityTokenValidators)
197+
{
198+
return AuthenticateResults.TokenHandlerUnableToValidate;
199+
}
200+
190201
return AuthenticateResults.ValidatorNotFound;
191202
}
192203
catch (Exception ex)
@@ -208,6 +219,47 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
208219
}
209220
}
210221

222+
private void RecordTokenValidationError(Exception exception, List<Exception> exceptions)
223+
{
224+
if (exception != null)
225+
{
226+
Logger.TokenValidationFailed(exception);
227+
exceptions.Add(exception);
228+
}
229+
230+
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
231+
// Refreshing on SecurityTokenSignatureKeyNotFound may be redundant if Last-Known-Good is enabled, it won't do much harm, most likely will be a nop.
232+
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
233+
&& exception is SecurityTokenSignatureKeyNotFoundException)
234+
{
235+
Options.ConfigurationManager.RequestRefresh();
236+
}
237+
}
238+
239+
private async Task<TokenValidationParameters> SetupTokenValidationParametersAsync()
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+
tokenValidationParameters.ConfigurationManager = baseConfigurationManager;
247+
}
248+
else
249+
{
250+
if (Options.ConfigurationManager != null)
251+
{
252+
// GetConfigurationAsync has a time interval that must pass before new http request will be issued.
253+
var configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
254+
var issuers = new[] { configuration.Issuer };
255+
tokenValidationParameters.ValidIssuers = (tokenValidationParameters.ValidIssuers == null ? issuers : tokenValidationParameters.ValidIssuers.Concat(issuers));
256+
tokenValidationParameters.IssuerSigningKeys = (tokenValidationParameters.IssuerSigningKeys == null ? configuration.SigningKeys : tokenValidationParameters.IssuerSigningKeys.Concat(configuration.SigningKeys));
257+
}
258+
}
259+
260+
return tokenValidationParameters;
261+
}
262+
211263
private static DateTime? GetSafeDateTime(DateTime dateTime)
212264
{
213265
// Assigning DateTime.MinValue or default(DateTime) to a DateTimeOffset when in a UTC+X timezone will throw

src/Security/Authentication/JwtBearer/src/JwtBearerOptions.cs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Net.Http;
66
using Microsoft.IdentityModel.Protocols;
77
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
8+
using Microsoft.IdentityModel.JsonWebTokens;
89
using Microsoft.IdentityModel.Tokens;
910

1011
namespace Microsoft.AspNetCore.Authentication.JwtBearer;
@@ -15,13 +16,22 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer;
1516
public class JwtBearerOptions : AuthenticationSchemeOptions
1617
{
1718
private readonly JwtSecurityTokenHandler _defaultHandler = new JwtSecurityTokenHandler();
19+
private readonly JsonWebTokenHandler _defaultTokenHandler = new JsonWebTokenHandler
20+
{
21+
MapInboundClaims = JwtSecurityTokenHandler.DefaultMapInboundClaims
22+
};
23+
24+
private bool _mapInboundClaims = JwtSecurityTokenHandler.DefaultMapInboundClaims;
1825

1926
/// <summary>
2027
/// Initializes a new instance of <see cref="JwtBearerOptions"/>.
2128
/// </summary>
2229
public JwtBearerOptions()
2330
{
31+
#pragma warning disable CS0618 // Type or member is obsolete
2432
SecurityTokenValidators = new List<ISecurityTokenValidator> { _defaultHandler };
33+
#pragma warning restore CS0618 // Type or member is obsolete
34+
TokenHandlers = new List<TokenHandler> { _defaultTokenHandler };
2535
}
2636

2737
/// <summary>
@@ -103,8 +113,14 @@ public JwtBearerOptions()
103113
/// <summary>
104114
/// Gets the ordered list of <see cref="ISecurityTokenValidator"/> used to validate access tokens.
105115
/// </summary>
116+
[Obsolete("SecurityTokenValidators is no longer used by default. Use TokenHandlers instead. To continue using SecurityTokenValidators, set UseSecurityTokenValidators to true. See https://aka.ms/aspnetcore8/security-token-changes")]
106117
public IList<ISecurityTokenValidator> SecurityTokenValidators { get; private set; }
107118

119+
/// <summary>
120+
/// Gets the ordered list of <see cref="TokenHandler"/> used to validate access tokens.
121+
/// </summary>
122+
public IList<TokenHandler> TokenHandlers { get; private set; }
123+
108124
/// <summary>
109125
/// Gets or sets the parameters used to validate identity tokens.
110126
/// </summary>
@@ -126,15 +142,20 @@ public JwtBearerOptions()
126142
public bool IncludeErrorDetails { get; set; } = true;
127143

128144
/// <summary>
129-
/// Gets or sets the <see cref="MapInboundClaims"/> property on the default instance of <see cref="JwtSecurityTokenHandler"/> in SecurityTokenValidators, which is used when determining
130-
/// whether or not to map claim types that are extracted when validating a <see cref="JwtSecurityToken"/>.
145+
/// Gets or sets the <see cref="MapInboundClaims"/> property on the default instance of <see cref="JwtSecurityTokenHandler"/> in SecurityTokenValidators, or <see cref="JsonWebTokenHandler"/> in TokenHandlers, which is used when determining
146+
/// whether or not to map claim types that are extracted when validating a <see cref="JwtSecurityToken"/> or a <see cref="JsonWebToken"/>.
131147
/// <para>If this is set to true, the Claim Type is set to the JSON claim 'name' after translating using this mapping. Otherwise, no mapping occurs.</para>
132148
/// <para>The default value is true.</para>
133149
/// </summary>
134150
public bool MapInboundClaims
135151
{
136-
get => _defaultHandler.MapInboundClaims;
137-
set => _defaultHandler.MapInboundClaims = value;
152+
get => _mapInboundClaims;
153+
set
154+
{
155+
_mapInboundClaims = value;
156+
_defaultHandler.MapInboundClaims = value;
157+
_defaultTokenHandler.MapInboundClaims = value;
158+
}
138159
}
139160

140161
/// <summary>
@@ -152,4 +173,17 @@ public bool MapInboundClaims
152173
/// Defaults to <see cref="ConfigurationManager{OpenIdConnectConfiguration}.DefaultRefreshInterval" />.
153174
/// </value>
154175
public TimeSpan RefreshInterval { get; set; } = ConfigurationManager<OpenIdConnectConfiguration>.DefaultRefreshInterval;
176+
177+
/// <summary>
178+
/// Gets or sets whether <see cref="TokenHandlers"/> or <see cref="SecurityTokenValidators"/> will be used to validate the inbound token.
179+
/// </summary>
180+
/// <remarks>
181+
/// The advantages of using TokenHandlers are:
182+
/// <para>There is an Async model.</para>
183+
/// <para>The default token handler is a <see cref="JsonWebTokenHandler"/> which is faster than a <see cref="JwtSecurityTokenHandler"/>.</para>
184+
/// <para>There is an ability to make use of a Last-Known-Good model for metadata that protects applications when metadata is published with errors.</para>
185+
/// SecurityTokenValidators can be used when <see cref="TokenValidatedContext.SecurityToken"/> needs a <see cref="JwtSecurityToken"/>.
186+
/// When using TokenHandlers, <see cref="TokenValidatedContext.SecurityToken"/> will be a <see cref="JsonWebToken"/>.
187+
/// </remarks>
188+
public bool UseSecurityTokenValidators { get; set; }
155189
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
#nullable enable
22
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.JwtBearerHandler(Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions!>! options, Microsoft.Extensions.Logging.ILoggerFactory! logger, System.Text.Encodings.Web.UrlEncoder! encoder) -> void
3+
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions.TokenHandlers.get -> System.Collections.Generic.IList<Microsoft.IdentityModel.Tokens.TokenHandler!>!
4+
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions.UseSecurityTokenValidators.get -> bool
5+
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions.UseSecurityTokenValidators.set -> void

0 commit comments

Comments
 (0)