Skip to content

Commit 5170c31

Browse files
authored
Add details to the JwtBearer error messages #4679 (#8259)
1 parent 435867e commit 5170c31

File tree

2 files changed

+102
-16
lines changed

2 files changed

+102
-16
lines changed

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Globalization;
67
using System.Linq;
78
using System.Security.Claims;
89
using System.Text;
@@ -284,23 +285,25 @@ private static string CreateErrorDescription(Exception authFailure)
284285
// and we want to display the most specific message possible.
285286
switch (ex)
286287
{
287-
case SecurityTokenInvalidAudienceException _:
288-
messages.Add("The audience is invalid");
288+
case SecurityTokenInvalidAudienceException stia:
289+
messages.Add($"The audience '{stia.InvalidAudience ?? "(null)"}' is invalid");
289290
break;
290-
case SecurityTokenInvalidIssuerException _:
291-
messages.Add("The issuer is invalid");
291+
case SecurityTokenInvalidIssuerException stii:
292+
messages.Add($"The issuer '{stii.InvalidIssuer ?? "(null)"}' is invalid");
292293
break;
293294
case SecurityTokenNoExpirationException _:
294295
messages.Add("The token has no expiration");
295296
break;
296-
case SecurityTokenInvalidLifetimeException _:
297-
messages.Add("The token lifetime is invalid");
297+
case SecurityTokenInvalidLifetimeException stil:
298+
messages.Add("The token lifetime is invalid; NotBefore: "
299+
+ $"'{stil.NotBefore?.ToString(CultureInfo.InvariantCulture) ?? "(null)"}'"
300+
+ $", Expires: '{stil.Expires?.ToString(CultureInfo.InvariantCulture) ?? "(null)"}'");
298301
break;
299-
case SecurityTokenNotYetValidException _:
300-
messages.Add("The token is not valid yet");
302+
case SecurityTokenNotYetValidException stnyv:
303+
messages.Add($"The token is not valid before '{stnyv.NotBefore.ToString(CultureInfo.InvariantCulture)}'");
301304
break;
302-
case SecurityTokenExpiredException _:
303-
messages.Add("The token is expired");
305+
case SecurityTokenExpiredException ste:
306+
messages.Add($"The token expired at '{ste.Expires.ToString(CultureInfo.InvariantCulture)}'");
304307
break;
305308
case SecurityTokenSignatureKeyNotFoundException _:
306309
messages.Add("The signature key was not found");

src/Security/Authentication/test/JwtBearerTests.cs

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,12 @@ public async Task InvalidTokenReceived()
239239
}
240240

241241
[Theory]
242-
[InlineData(typeof(SecurityTokenInvalidAudienceException), "The audience is invalid")]
243-
[InlineData(typeof(SecurityTokenInvalidIssuerException), "The issuer is invalid")]
242+
[InlineData(typeof(SecurityTokenInvalidAudienceException), "The audience '(null)' is invalid")]
243+
[InlineData(typeof(SecurityTokenInvalidIssuerException), "The issuer '(null)' is invalid")]
244244
[InlineData(typeof(SecurityTokenNoExpirationException), "The token has no expiration")]
245-
[InlineData(typeof(SecurityTokenInvalidLifetimeException), "The token lifetime is invalid")]
246-
[InlineData(typeof(SecurityTokenNotYetValidException), "The token is not valid yet")]
247-
[InlineData(typeof(SecurityTokenExpiredException), "The token is expired")]
245+
[InlineData(typeof(SecurityTokenInvalidLifetimeException), "The token lifetime is invalid; NotBefore: '(null)', Expires: '(null)'")]
246+
[InlineData(typeof(SecurityTokenNotYetValidException), "The token is not valid before '01/01/0001 00:00:00'")]
247+
[InlineData(typeof(SecurityTokenExpiredException), "The token expired at '01/01/0001 00:00:00'")]
248248
[InlineData(typeof(SecurityTokenInvalidSignatureException), "The signature is invalid")]
249249
[InlineData(typeof(SecurityTokenSignatureKeyNotFoundException), "The signature key was not found")]
250250
public async Task ExceptionReportedInHeaderForAuthenticationFailures(Type errorType, string message)
@@ -261,6 +261,26 @@ public async Task ExceptionReportedInHeaderForAuthenticationFailures(Type errorT
261261
Assert.Equal("", response.ResponseText);
262262
}
263263

264+
[Theory]
265+
[InlineData(typeof(SecurityTokenInvalidAudienceException), "The audience 'Bad Audience' is invalid")]
266+
[InlineData(typeof(SecurityTokenInvalidIssuerException), "The issuer 'Bad Issuer' is invalid")]
267+
[InlineData(typeof(SecurityTokenInvalidLifetimeException), "The token lifetime is invalid; NotBefore: '01/15/2001 00:00:00', Expires: '02/20/2000 00:00:00'")]
268+
[InlineData(typeof(SecurityTokenNotYetValidException), "The token is not valid before '01/15/2045 00:00:00'")]
269+
[InlineData(typeof(SecurityTokenExpiredException), "The token expired at '02/20/2000 00:00:00'")]
270+
public async Task ExceptionReportedInHeaderWithDetailsForAuthenticationFailures(Type errorType, string message)
271+
{
272+
var server = CreateServer(options =>
273+
{
274+
options.SecurityTokenValidators.Clear();
275+
options.SecurityTokenValidators.Add(new DetailedInvalidTokenValidator(errorType));
276+
});
277+
278+
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
279+
Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode);
280+
Assert.Equal($"Bearer error=\"invalid_token\", error_description=\"{message}\"", response.Response.Headers.WwwAuthenticate.First().ToString());
281+
Assert.Equal("", response.ResponseText);
282+
}
283+
264284
[Theory]
265285
[InlineData(typeof(ArgumentException))]
266286
public async Task ExceptionNotReportedInHeaderForOtherFailures(Type errorType)
@@ -289,7 +309,7 @@ public async Task ExceptionsReportedInHeaderForMultipleAuthenticationFailures()
289309

290310
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
291311
Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode);
292-
Assert.Equal("Bearer error=\"invalid_token\", error_description=\"The audience is invalid; The signature key was not found\"",
312+
Assert.Equal("Bearer error=\"invalid_token\", error_description=\"The audience '(null)' is invalid; The signature key was not found\"",
293313
response.Response.Headers.WwwAuthenticate.First().ToString());
294314
Assert.Equal("", response.ResponseText);
295315
}
@@ -691,6 +711,69 @@ public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParame
691711
}
692712
}
693713

714+
class DetailedInvalidTokenValidator : ISecurityTokenValidator
715+
{
716+
public DetailedInvalidTokenValidator()
717+
{
718+
ExceptionType = typeof(SecurityTokenException);
719+
}
720+
721+
public DetailedInvalidTokenValidator(Type exceptionType)
722+
{
723+
ExceptionType = exceptionType;
724+
}
725+
726+
public Type ExceptionType { get; set; }
727+
728+
public bool CanValidateToken => true;
729+
730+
public int MaximumTokenSizeInBytes
731+
{
732+
get { throw new NotImplementedException(); }
733+
set { throw new NotImplementedException(); }
734+
}
735+
736+
public bool CanReadToken(string securityToken) => true;
737+
738+
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
739+
{
740+
if (ExceptionType == typeof(SecurityTokenInvalidAudienceException))
741+
{
742+
throw new SecurityTokenInvalidAudienceException("SecurityTokenInvalidAudienceException") { InvalidAudience = "Bad Audience" };
743+
}
744+
if (ExceptionType == typeof(SecurityTokenInvalidIssuerException))
745+
{
746+
throw new SecurityTokenInvalidIssuerException("SecurityTokenInvalidIssuerException") { InvalidIssuer = "Bad Issuer" };
747+
}
748+
if (ExceptionType == typeof(SecurityTokenInvalidLifetimeException))
749+
{
750+
throw new SecurityTokenInvalidLifetimeException("SecurityTokenInvalidLifetimeException")
751+
{
752+
NotBefore = new DateTime(2001, 1, 15),
753+
Expires = new DateTime(2000, 2, 20),
754+
};
755+
}
756+
if (ExceptionType == typeof(SecurityTokenNotYetValidException))
757+
{
758+
throw new SecurityTokenNotYetValidException("SecurityTokenNotYetValidException")
759+
{
760+
NotBefore = new DateTime(2045, 1, 15),
761+
};
762+
}
763+
if (ExceptionType == typeof(SecurityTokenExpiredException))
764+
{
765+
throw new SecurityTokenExpiredException("SecurityTokenExpiredException")
766+
{
767+
Expires = new DateTime(2000, 2, 20),
768+
};
769+
}
770+
else
771+
{
772+
throw new NotImplementedException(ExceptionType.Name);
773+
}
774+
}
775+
}
776+
694777
class BlobTokenValidator : ISecurityTokenValidator
695778
{
696779
private Action<string> _tokenValidator;

0 commit comments

Comments
 (0)