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.
|
/// Any failures encountered during the authentication process.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Exception AuthenticateFailure { get; set; }
|
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,
|
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);
|
await Options.Events.Challenge(eventContext);
|
||||||
if (eventContext.HandledResponse)
|
if (eventContext.HandledResponse)
|
||||||
{
|
{
|
||||||
|
|
@ -203,9 +211,9 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
||||||
|
|
||||||
Response.StatusCode = 401;
|
Response.StatusCode = 401;
|
||||||
|
|
||||||
var errorDescription = CreateErrorDescription(eventContext.AuthenticateFailure);
|
if (string.IsNullOrEmpty(eventContext.Error) &&
|
||||||
|
string.IsNullOrEmpty(eventContext.ErrorDescription) &&
|
||||||
if (errorDescription.Length == 0)
|
string.IsNullOrEmpty(eventContext.ErrorUri))
|
||||||
{
|
{
|
||||||
Response.Headers.Append(HeaderNames.WWWAuthenticate, Options.Challenge);
|
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
|
// Only add a comma after the first param, if any
|
||||||
builder.Append(',');
|
builder.Append(',');
|
||||||
}
|
}
|
||||||
builder.Append(" error=\"invalid_token\", error_description=\"");
|
if (!string.IsNullOrEmpty(eventContext.Error))
|
||||||
builder.Append(errorDescription);
|
{
|
||||||
builder.Append('\"');
|
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());
|
Response.Headers.Append(HeaderNames.WWWAuthenticate, builder.ToString());
|
||||||
}
|
}
|
||||||
|
|
@ -231,11 +265,6 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
||||||
|
|
||||||
private static string CreateErrorDescription(Exception authFailure)
|
private static string CreateErrorDescription(Exception authFailure)
|
||||||
{
|
{
|
||||||
if (authFailure == null)
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<Exception> exceptions;
|
IEnumerable<Exception> exceptions;
|
||||||
if (authFailure is AggregateException)
|
if (authFailure is AggregateException)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -118,5 +118,12 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
/// <see cref="Http.Authentication.AuthenticationProperties"/> after a successful authorization.
|
/// <see cref="Http.Authentication.AuthenticationProperties"/> after a successful authorization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SaveToken { get; set; } = true;
|
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.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
@ -127,7 +128,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
||||||
|
|
||||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode);
|
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);
|
Assert.Equal("", response.ResponseText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,7 +165,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
||||||
|
|
||||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode);
|
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);
|
Assert.Equal("", response.ResponseText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,11 +180,100 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
|
||||||
|
|
||||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode);
|
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());
|
response.Response.Headers.WwwAuthenticate.First().ToString());
|
||||||
Assert.Equal("", response.ResponseText);
|
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]
|
[Fact]
|
||||||
public async Task CustomTokenValidated()
|
public async Task CustomTokenValidated()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue