Add a new IncludeErrorDetails option to prevent the JWT middleware from returning error/error_description

This commit is contained in:
Kévin Chalet 2016-06-02 16:45:05 +02:00
parent 35d0592701
commit 120021e8a3
4 changed files with 160 additions and 14 deletions

View File

@ -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; }
}
}

View File

@ -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)
{

View File

@ -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;
}
}

View File

@ -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()
{