Log middle-ware gets 200 response code when it is actually a 500 - asp.net-core

I am working on a ASP.Net Core Web API project and I want to log all the requests and 500 server errors.
I used custom middleware to log requests, and it is defined in the startup.cs as:
app.UseMiddleware<logMiddleware>();
I also defined an Exception handler to capture server errors in the startup.cs:
app.UseExceptionHandler(builder => {
builder.Run(async context => {
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null) {
logService logger = new logService(conf);
await logger.logError(error, context);
}
});
});
I keep the request information in error logs, so I don't need to save the request log when the response code is 500, so I added a check into request log function to filter errors:
public async Task logRequestAsync(HttpContext context) {
if (context.Response.StatusCode != 500) {
//do things
}
}
The problem is the Response.StatusCode returns as 200 instead of 500. Probably it runs before the API call's function is completed and the server error happens later in the runtime.
Is there a way to move the "request log" process to the point where the response is created instead of the beginning of the API request?

Related

Trigger exception handler (with status code) from middleware

I'm trying to trigger an exception (handled by /Error) from a middleware in ASP.NET Core 3.1.
Exception handler is registered in Startup.cs (as shown below) without app.UseDeveloperExceptionPage(), and works well otherwise.
app.UseExceptionHandler("/Error");
app.UseStatusCodePagesWithReExecute("/Error", "?statusCode={0}");
But how do I trigger an exception (with an HTTP status code) from a middleware's Invoke method?
Update (solution): I mainly wanted to raise an exception from a custom middleware, but also pass a status code to the exception handler (/Error). I got it working with this code in the middleware:
public async Task Invoke(HttpContext context)
{
if (context?.Request?.Path.Value.StartsWith("/error"))
{
// allow processing of exception handler
await _next(context);
return;
}
// skip processing, and set status code for use in exception handler
context.SetEndpoint(endpoint: null);
context.Response.StatusCode = 503;
}
According to the document, if the server catches an exception before response headers are sent, the server sends a 500 - Internal Server Error response without a response body.
If the server catches an exception after response headers are sent, the server closes the connection.
Requests that aren't handled by the app are handled by the server.
Any exception that occurs when the server is handling the request is handled by the server's exception handling. The app's custom error pages, exception handling middleware, and filters don't affect this behavior.
If you directly throw the exception at the application start, it will not goes to the exception handler.
Like below middleware:
app.Use(async (context, next) =>
{
throw new ArgumentException("aaa");
await next();
});
If you throw the exception is thrown after the application has completely started, it will go to the exception handler like below example. If you type home/privacy in your url, you will find it goes to the exception handler page.
app.Use(async (context, next) =>
{
if (context.Request.Path == "/Home/Privacy")
{
throw new ArgumentException("aaa");
}
await next();
});
Result:
I mainly wanted to raise an exception from a custom middleware, but also pass a status code to the exception handler (/Error). I got it working with this code in the middleware:
public async Task Invoke(HttpContext context)
{
if (context?.Request?.Path.Value.StartsWith("/error"))
{
// allow processing of exception handler
await _next(context);
return;
}
// skip processing, and set status code for use in exception handler
context.SetEndpoint(endpoint: null);
context.Response.StatusCode = 503;
}

Middleware writes response when starting up the project

Using ASP.Net Core, C#
I have a middleware where i check for particular cookie is present otherwise returning a 400 response. My problem is the middleware fires up starting the project itself and check the cookie is present or not and then shows the response text in the swagger index page, which i dont want.the middleware fies when swagger loads. I want this condition to be executed only for the requests.
public async Task InvokeAsync(HttpContext context)
{
var pl = context.Request.Cookies["pl"];
var sig = context.Request.Cookies["sig"];
if (string.IsNullOrWhiteSpace(pl) || string.IsNullOrWhiteSpace(sig))
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("Invalid Data");
return;
}
// If success i process and do something
// Call the next delegate/middleware in the pipeline
await _next(context);
}
I can prevent firing the middleware when swagger loads and fire only for api requests. But is this better approach or any other better are there.
app.UseWhen(context => `context.Request.Path.ToString().Contains("/api"),HandleBranch);`

ASP.NET Core modify heads on exception

If I throw a BadLabUpdateException then I'd like the response headers of the request to include the exception details. I tried to do this in my Configure method:
app.UseExceptionHandler(errorApp => {
errorApp.Run(async context => {
var pathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
if (pathFeature is BadLabUpdateException ex) {
context.Response.StatusCode = 400;
context.Response.Headers.Add("X-Display-To-User", new StringValues(ex.Message));
}
});
});
That doesn't have any effect though.
The goal is essentially if I throw that specific exception from the server, then the message will contain the exact string that the client should display to the end user.
I need this to work both in development mode and production.

only http status been caught and not the JSON error response

The backend API responds with HTTP Status 400 and error messages as follows:
{detail: 'msg'}
It works well on web portal but with the following code, I can't read the response data:
try {
let res = await axios.post(APIS.START.LOGIN, loginData);
} catch (e) {
console.log(69, e);
}
You can access the body of the failed request by referencing e.response.data.
Simply replace console.log(69, e) with console.log(69, e.response.data)

How to customize the authorization error produced by OpenIddict?

I'm using OpenIddict for auth in a .NET Core 2 API. Client side I'm relying on any API errors to follow a custom scheme. However, when e.g. a refresh token has been outdated, I can't seem to find out how to customize the error sent back.
The /token endpoint is never reached, so the error is not under "my control".
The result of the request is a status code 400, with the following JSON:
{"error":"invalid_grant","error_description":"The specified refresh token is no longer valid."}
I've tried to use a custom middleware to catch all status codes (which it does), but the result is returned before the execution of my custom middleware has completed.
How can I properly customize the error or intercept to change it? Thanks!
You can use OpenIddict's event model to customize the token response payloads before they are written to the response stream. Here's an example:
MyApplyTokenResponseHandler.cs
public class MyApplyTokenResponseHandler : IOpenIddictServerEventHandler<ApplyTokenResponseContext>
{
public ValueTask HandleAsync(ApplyTokenResponseContext context)
{
var response = context.Response;
if (string.Equals(response.Error, OpenIddictConstants.Errors.InvalidGrant, StringComparison.Ordinal) &&
!string.IsNullOrEmpty(response.ErrorDescription))
{
response.ErrorDescription = "Your customized error";
}
return default;
}
}
Startup.cs
services.AddOpenIddict()
.AddCore(options =>
{
// ...
})
.AddServer(options =>
{
// ...
options.AddEventHandler<ApplyTokenResponseContext>(builder =>
builder.UseSingletonHandler<MyApplyTokenResponseHandler>());
})
.AddValidation();
The /token endpoint is never reached, so the error is not under "my control".
In fact ,the /token is reached, and the parameter of grant_type equals refresh_token. But the rejection logic when refresh token expired is not processed by us. It is some kind of "hardcoded" in source code :
if (token == null)
{
context.Reject(
error: OpenIddictConstants.Errors.InvalidGrant,
description: context.Request.IsAuthorizationCodeGrantType() ?
"The specified authorization code is no longer valid." :
"The specified refresh token is no longer valid.");
return;
}
if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
{
if (!await TryRedeemTokenAsync(token))
{
context.Reject(
error: OpenIddictConstants.Errors.InvalidGrant,
description: context.Request.IsAuthorizationCodeGrantType() ?
"The specified authorization code is no longer valid." :
"The specified refresh token is no longer valid.");
return;
}
}
The context.Reject here comes from the assembly AspNet.Security.OpenIdConnect.Server.
For more details, see source code on GitHub .
I've tried to use a custom middleware to catch all status codes (which it does), but the result is returned before the execution of my custom middleware has completed.
I've tried and I'm pretty sure we can use a custom middleware to catch all status codes. The key point is to detect the status code after the next() invocation:
app.Use(async(context , next )=>{
// passby all other end points
if(! context.Request.Path.StartsWithSegments("/connect/token")){
await next();
return;
}
// since we might want to detect the Response.Body, I add some stream here .
// if you only want to detect the status code , there's no need to use these streams
Stream originalStream = context.Response.Body;
var hijackedStream = new MemoryStream();
context.Response.Body = hijackedStream;
hijackedStream.Seek(0,SeekOrigin.Begin);
await next();
// if status code not 400 , pass by
if(context.Response.StatusCode != 400){
await CopyStreamToResponseBody(context,hijackedStream,originalStream);
return;
}
// read and custom the stream
hijackedStream.Seek(0,SeekOrigin.Begin);
using (StreamReader sr = new StreamReader(hijackedStream))
{
var raw= sr.ReadToEnd();
if(raw.Contains("The specified refresh token is no longer valid.")){
// custom your own response
context.Response.StatusCode = 401;
// ...
//context.Response.Body = ... /
}else{
await CopyStreamToResponseBody(context,hijackedStream,originalStream);
}
}
});
// helper to make the copy easy
private async Task CopyStreamToResponseBody(HttpContext context,Stream newStream, Stream originalStream){
newStream.Seek(0,SeekOrigin.Begin);
await newStream.CopyToAsync(originalStream);
context.Response.ContentLength =originalStream.Length;
context.Response.Body = originalStream;
}