From 4fe2c6658735e33d30811a1ff4b1ead324de5de8 Mon Sep 17 00:00:00 2001 From: Brennan Date: Fri, 18 Jun 2021 15:56:27 -0700 Subject: [PATCH] Fix JWT DateTimeOffset handling with DateTime.MinValue --- .../JwtBearer/src/JwtBearerHandler.cs | 15 ++++++- .../Authentication/test/JwtBearerTests.cs | 43 ++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs b/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs index 9476bd441ed6..f7a5dfacef00 100644 --- a/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs +++ b/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs @@ -143,8 +143,8 @@ protected override async Task HandleAuthenticateAsync() SecurityToken = validatedToken }; - tokenValidatedContext.Properties.ExpiresUtc = validatedToken.ValidTo; - tokenValidatedContext.Properties.IssuedUtc = validatedToken.ValidFrom; + tokenValidatedContext.Properties.ExpiresUtc = GetSafeDateTime(validatedToken.ValidTo); + tokenValidatedContext.Properties.IssuedUtc = GetSafeDateTime(validatedToken.ValidFrom); await Events.TokenValidated(tokenValidatedContext); if (tokenValidatedContext.Result != null) @@ -202,6 +202,17 @@ protected override async Task HandleAuthenticateAsync() } } + private static DateTime? GetSafeDateTime(DateTime dateTime) + { + // Assigning DateTime.MinValue or default(DateTime) to a DateTimeOffset when in a UTC+X timezone will throw + // Since we don't really care about DateTime.MinValue in this case let's just set the field to null + if (dateTime == DateTime.MinValue) + { + return null; + } + return dateTime; + } + /// protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { diff --git a/src/Security/Authentication/test/JwtBearerTests.cs b/src/Security/Authentication/test/JwtBearerTests.cs index a83411b1741a..dd21d7b2cb1a 100755 --- a/src/Security/Authentication/test/JwtBearerTests.cs +++ b/src/Security/Authentication/test/JwtBearerTests.cs @@ -846,6 +846,47 @@ public async Task ExpirationAndIssuedSetOnAuthenticateResult() Assert.Equal(token.ValidFrom, dom.RootElement.GetProperty("issued").GetDateTimeOffset()); } + [Fact] + public async Task ExpirationAndIssuedNullWhenMinOrMaxValue() + { + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(new string('a', 128))); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, "Bob") + }; + + var token = new JwtSecurityToken( + issuer: "issuer.contoso.com", + audience: "audience.contoso.com", + claims: claims, + expires: DateTime.MaxValue, + signingCredentials: creds); + + var tokenText = new JwtSecurityTokenHandler().WriteToken(token); + + using var host = await CreateHost(o => + { + o.SaveToken = true; + o.TokenValidationParameters = new TokenValidationParameters() + { + ValidIssuer = "issuer.contoso.com", + ValidAudience = "audience.contoso.com", + IssuerSigningKey = key, + }; + }); + + var newBearerToken = "Bearer " + tokenText; + using var server = host.GetTestServer(); + var response = await SendAsync(server, "http://example.com/expiration", newBearerToken); + Assert.Equal(HttpStatusCode.OK, response.Response.StatusCode); + var responseBody = await response.Response.Content.ReadAsStringAsync(); + using var dom = JsonDocument.Parse(responseBody); + Assert.Equal(JsonValueKind.Null, dom.RootElement.GetProperty("expires").ValueKind); + Assert.Equal(JsonValueKind.Null, dom.RootElement.GetProperty("issued").ValueKind); + } + class InvalidTokenValidator : ISecurityTokenValidator { public InvalidTokenValidator() @@ -1065,7 +1106,7 @@ private static async Task CreateHost(Action options = n { var authenticationResult = await context.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme); await context.Response.WriteAsJsonAsync( - new { Expires = authenticationResult.Properties.ExpiresUtc, Issued = authenticationResult.Properties.IssuedUtc }); + new { Expires = authenticationResult.Properties?.ExpiresUtc, Issued = authenticationResult.Properties?.IssuedUtc }); } else {