0

When I get an automated 400 from Asp.Net Core, there are often some implementation details that I do not want to expose, nor are they relevant really, e.g.:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "00-71b3ed06990f759c440ed484475b437c-23db588b254e8013-00",
    "errors": {
        "$": [
            "JSON deserialization for type 'MyExampleNamespace.MyRequest' was missing required properties, including the following: messageId"
        ],
        "request": [
            "The request field is required."
        ]
    }
}
public record MyRequest
{
   public required MessageId { get; init; }
}

Example request: json

{
   "notMessageId": "hello"
}

So what I expect to get is a more "non C# dependent response" that does not include fully qualified C# names in response.

Ilya Chernomordik
  • 27,817
  • 27
  • 121
  • 207
  • 1
    ASP.NET Core doesn't display exception details in production. Have you tried this in a production environment? What is the *actual* response? If the request is invalid you'd get [an IETF-standard ProblemDetails response](https://learn.microsoft.com/en-us/aspnet/core/web-api/handle-errors?view=aspnetcore-7.0#default-problem-details-response). Even that can be customized though – Panagiotis Kanavos Nov 18 '22 at 12:07
  • It surely does in this case and I do not use Development environment. I will add the full response as this probably makes it not quite clear – Ilya Chernomordik Nov 18 '22 at 12:13
  • I linked to the docs that show how the 400 ProblemDetails response is generated and customized. One of these ways is [through the Problem Details service](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-7.0#problem-details) which allows you to [write a completely custom writer](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-7.0#custom-iproblemdetailswriter) or modify the `ProblemDetails` data before it's returned – Panagiotis Kanavos Nov 18 '22 at 12:15
  • That *is* the ProblemDetails response, so what you really need is to customize it. – Panagiotis Kanavos Nov 18 '22 at 12:17
  • Ok, thanks I will take a look on how I can do that, I was hoping for something simpler though – Ilya Chernomordik Nov 18 '22 at 12:18
  • Are you sure it's worth it to just change a few words the error description? The Problem Details service already does the hard work - it will include any validation problem detected through validation attributes and generate a standardized response. That's something that can be used by clients, even applications, to identify the problems. These errors will match the OpenAPI schema too – Panagiotis Kanavos Nov 18 '22 at 12:24
  • It may be possible to modify the description text through the validation attributes too – Panagiotis Kanavos Nov 18 '22 at 12:26
  • I do not want to change all that, I just want it to not expose the names of my classes, e.g.: "JSON deserialization for type 'MyNamespaece.MyType' was missing required properties, including the following: messageId" This gives the full namespace name which can potentially be used in some attack – Ilya Chernomordik Nov 18 '22 at 12:26
  • It is probably not worth using time on it in my case, but if it would have been a simple flag I would have used it – Ilya Chernomordik Nov 18 '22 at 12:27
  • You aren't exposing the names of your classes. Those are the DTOs you already expose through OpenAPI/Swagger. How are *clients* going to work with the API if they don't know what fields and classes are used? – Panagiotis Kanavos Nov 18 '22 at 12:29
  • If the deserialization fails, the fully qualified name of a C# class is exposed, not only the properties it contains – Ilya Chernomordik Nov 18 '22 at 12:30
  • If you want to remove all documentation you can remove Swagger and the Problem Details service. The doc links show how to do that as well. If you do that though, you'll have trouble debugging your own application as validation errors won't contain anything useful any more – Panagiotis Kanavos Nov 18 '22 at 12:30
  • Can you provide a *concrete* example? DTO, JSON and the generated response? – Panagiotis Kanavos Nov 18 '22 at 12:31
  • I have updated the question. It is by the way the same with previous .Net as well, if you e.g. send a boolean where string is expected, so nothing "required" specific – Ilya Chernomordik Nov 18 '22 at 12:35
  • I don't understand what the problem is then. If you want to change the type name just change the type name and namespace to match what you want. That's not a leak. Again, the type name is already known to the caller through the Swagger doc. The whole point of the validation error response is to tell the caller, which may be an SPA, what's wrong so it can display it to the user. – Panagiotis Kanavos Nov 18 '22 at 12:38
  • The DTO schema is not the domain or database schema. It can, and probably *should* be different to match the caller's expectations – Panagiotis Kanavos Nov 18 '22 at 12:40
  • Well it is not a big deal of course, but the caller does not need to know fully qualified namespace, it is just irrelevant information – Ilya Chernomordik Nov 18 '22 at 12:42
  • Hi, I also think you need custom ProblemDetails, reference: https://stackoverflow.com/a/64347354/11398810 – Rena Nov 21 '22 at 09:32

2 Answers2

2

Do a custom implementation for errors:

public class ErrorHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    public ErrorHandlerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
    {
        _next = next;
        _logger = loggerFactory.CreateLogger<ErrorHandlerMiddleware>();
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception error)
        {

            var problemDetails = new ProblemDetails
            {
                Title = "An unexpected error occurred!",
                Detail = error.GetType().Name,
                Status = StatusCodes.Status400BadRequest,
                Extensions =
                {
                    ["trace"] = Activity.Current?.Id ?? context?.TraceIdentifier
                }
            };


            context.Response.ContentType = "application/problem+json";
            context.Response.StatusCode = problemDetails.Status.Value;
            context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
            {
                NoCache = true,
            };
            await JsonSerializer.SerializeAsync(context.Response.Body, problemDetails);
        } 
    } 
}

I would use inside the catch a switch case based on error type and redefine the error message output.

Use it like this: app.UseMiddleware<ErrorHandlerMiddleware>();.

D A
  • 1,724
  • 1
  • 8
  • 19
1

Turn off UseDeveloperExceptionPage. With this, you will see the detailed exceptions only in the dev environment. In production you will see whatever you define in the Error page

if (env.IsDevelopment())  
{  
     app.UseDeveloperExceptionPage();  
}  
else  
{  
     app.UseExceptionHandler("/Error");  
}  
Mayur Ekbote
  • 1,700
  • 1
  • 11
  • 14