Add a new IncludeErrorDetails option to prevent the JWT middleware from returning error/error_description
This commit is contained in:
parent
35d0592701
commit
120021e8a3
|
|
@ -22,5 +22,25 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
|||
/// Any failures encountered during the authentication process.
|
||||
/// </summary>
|
||||
public Exception AuthenticateFailure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the "error" value returned to the caller as part
|
||||
/// of the WWW-Authenticate header. This property may be null when
|
||||
/// <see cref="JwtBearerOptions.IncludeErrorDetails"/> is set to <c>false</c>.
|
||||
/// </summary>
|
||||
public string Error { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the "error_description" value returned to the caller as part
|
||||
/// of the WWW-Authenticate header. This property may be null when
|
||||
/// <see cref="JwtBearerOptions.IncludeErrorDetails"/> is set to <c>false</c>.
|
||||
/// </summary>
|
||||
public string ErrorDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the "error_uri" value returned to the caller as part of the
|
||||
/// WWW-Authenticate header. This property is always null unless explicitly set.
|
||||
/// </summary>
|
||||
public string ErrorUri { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,6 +191,14 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
|||
{
|
||||
AuthenticateFailure = authResult?.Failure,
|
||||
};
|
||||
|
||||
// Avoid returning error=invalid_token if the error is not caused by an authentication failure (e.g missing token).
|
||||
if (Options.IncludeErrorDetails && eventContext.AuthenticateFailure != null)
|
||||
{
|
||||
eventContext.Error = "invalid_token";
|
||||
eventContext.ErrorDescription = CreateErrorDescription(eventContext.AuthenticateFailure);
|
||||
}
|
||||
|
||||
await Options.Events.Challenge(eventContext);
|
||||
if (eventContext.HandledResponse)
|
||||
{
|
||||
|
|
@ -203,9 +211,9 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
|||
|
||||
Response.StatusCode = 401;
|
||||
|
||||
var errorDescription = CreateErrorDescription(eventContext.AuthenticateFailure);
|
||||
|
||||
if (errorDescription.Length == 0)
|
||||
if (string.IsNullOrEmpty(eventContext.Error) &&
|
||||
string.IsNullOrEmpty(eventContext.ErrorDescription) &&
|
||||
string.IsNullOrEmpty(eventContext.ErrorUri))
|
||||
{
|
||||
Response.Headers.Append(HeaderNames.WWWAuthenticate, Options.Challenge);
|
||||
}
|
||||
|
|
@ -219,9 +227,35 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
|||
// Only add a comma after the first param, if any
|
||||
builder.Append(',');
|
||||
}
|
||||
builder.Append(" error=\"invalid_token\", error_description=\"");
|
||||
builder.Append(errorDescription);
|
||||
builder.Append('\"');
|
||||
if (!string.IsNullOrEmpty(eventContext.Error))
|
||||
{
|
||||
builder.Append(" error=\"");
|
||||
builder.Append(eventContext.Error);
|
||||
builder.Append("\"");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(eventContext.ErrorDescription))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(eventContext.Error))
|
||||
{
|
||||
builder.Append(",");
|
||||
}
|
||||
|
||||
builder.Append(" error_description=\"");
|
||||
builder.Append(eventContext.ErrorDescription);
|
||||
builder.Append('\"');
|
||||
}
|
||||
if (!string.IsNullOrEmpty(eventContext.ErrorUri))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(eventContext.Error) ||
|
||||
!string.IsNullOrEmpty(eventContext.ErrorDescription))
|
||||
{
|
||||
builder.Append(",");
|
||||
}
|
||||
|
||||
builder.Append(" error_uri=\"");
|
||||
builder.Append(eventContext.ErrorUri);
|
||||
builder.Append('\"');
|
||||
}
|
||||
|
||||
Response.Headers.Append(HeaderNames.WWWAuthenticate, builder.ToString());
|
||||
}
|
||||
|
|
@ -231,11 +265,6 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
|||
|
||||
private static string CreateErrorDescription(Exception authFailure)
|
||||
{
|
||||
if (authFailure == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
IEnumerable<Exception> exceptions;
|
||||
if (authFailure is AggregateException)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -118,5 +118,12 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// <see cref="Http.Authentication.AuthenticationProperties"/> after a successful authorization.
|
||||
/// </summary>
|
||||
public bool SaveToken { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Defines whether the token validation errors should be returned to the caller.
|
||||
/// Enabled by default, this option can be disabled to prevent the JWT middleware
|
||||
/// from returning an error and an error_description in the WWW-Authenticate header.
|
||||
/// </summary>
|
||||
public bool IncludeErrorDetails { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Net;
|
|||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
|
@ -127,7 +128,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
|||
|
||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode);
|
||||
Assert.Equal("Bearer", response.Response.Headers.WwwAuthenticate.First().ToString());
|
||||
Assert.Equal("Bearer error=\"invalid_token\"", response.Response.Headers.WwwAuthenticate.First().ToString());
|
||||
Assert.Equal("", response.ResponseText);
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +165,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
|||
|
||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode);
|
||||
Assert.Equal("Bearer", response.Response.Headers.WwwAuthenticate.First().ToString());
|
||||
Assert.Equal("Bearer error=\"invalid_token\"", response.Response.Headers.WwwAuthenticate.First().ToString());
|
||||
Assert.Equal("", response.ResponseText);
|
||||
}
|
||||
|
||||
|
|
@ -179,11 +180,100 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
|||
|
||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode);
|
||||
Assert.Equal($"Bearer error=\"invalid_token\", error_description=\"The audience is invalid; The signature key was not found\"",
|
||||
Assert.Equal("Bearer error=\"invalid_token\", error_description=\"The audience is invalid; The signature key was not found\"",
|
||||
response.Response.Headers.WwwAuthenticate.First().ToString());
|
||||
Assert.Equal("", response.ResponseText);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("custom_error", "custom_description", "custom_uri")]
|
||||
[InlineData("custom_error", "custom_description", null)]
|
||||
[InlineData("custom_error", null, null)]
|
||||
[InlineData(null, "custom_description", "custom_uri")]
|
||||
[InlineData(null, "custom_description", null)]
|
||||
[InlineData(null, null, "custom_uri")]
|
||||
public async Task ExceptionsReportedInHeaderExposesUserDefinedError(string error, string description, string uri)
|
||||
{
|
||||
var options = new JwtBearerOptions
|
||||
{
|
||||
Events = new JwtBearerEvents
|
||||
{
|
||||
OnChallenge = context =>
|
||||
{
|
||||
context.Error = error;
|
||||
context.ErrorDescription = description;
|
||||
context.ErrorUri = uri;
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
var server = CreateServer(options);
|
||||
|
||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode);
|
||||
Assert.Equal("", response.ResponseText);
|
||||
|
||||
var builder = new StringBuilder(options.Challenge);
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
builder.Append(" error=\"");
|
||||
builder.Append(error);
|
||||
builder.Append("\"");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(description))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
builder.Append(",");
|
||||
}
|
||||
|
||||
builder.Append(" error_description=\"");
|
||||
builder.Append(description);
|
||||
builder.Append('\"');
|
||||
}
|
||||
if (!string.IsNullOrEmpty(uri))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(error) ||
|
||||
!string.IsNullOrEmpty(description))
|
||||
{
|
||||
builder.Append(",");
|
||||
}
|
||||
|
||||
builder.Append(" error_uri=\"");
|
||||
builder.Append(uri);
|
||||
builder.Append('\"');
|
||||
}
|
||||
|
||||
Assert.Equal(builder.ToString(), response.Response.Headers.WwwAuthenticate.First().ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExceptionNotReportedInHeaderWhenIncludeErrorDetailsIsFalse()
|
||||
{
|
||||
var server = CreateServer(new JwtBearerOptions
|
||||
{
|
||||
IncludeErrorDetails = false
|
||||
});
|
||||
|
||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode);
|
||||
Assert.Equal("Bearer", response.Response.Headers.WwwAuthenticate.First().ToString());
|
||||
Assert.Equal("", response.ResponseText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExceptionNotReportedInHeaderWhenTokenWasMissing()
|
||||
{
|
||||
var server = CreateServer(new JwtBearerOptions());
|
||||
|
||||
var response = await SendAsync(server, "http://example.com/oauth");
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode);
|
||||
Assert.Equal("Bearer", response.Response.Headers.WwwAuthenticate.First().ToString());
|
||||
Assert.Equal("", response.ResponseText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CustomTokenValidated()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue