Azure Application Insights is a very powerful APM tool for monitoring web applications. However, not all features that we sometimes require come out of the box. For example, in order to log request and response body, we have to write a custom ASP.NET Core middleware as explained in this post.
I now have an ASP.NET Core Web API application that uses JWT authentication. I would like to log user's identity when there is a failed request. Let's see how to do it.
Extending the Middleware
I am going to modify the ASP.NET Core Middleware from https://www.azureblue.io/how-to-log-http-request-body-with-asp-net-core-application-insights/
The original code logs request body as a Custom Property to Azure Application Insights.
public class RequestBodyLoggingMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var method = context.Request.Method;
context.Request.EnableBuffering();
if (context.Request.Body.CanRead && (method == HttpMethods.Post || method == HttpMethods.Put))
{
using var reader = new StreamReader(
context.Request.Body,
Encoding.UTF8,
detectEncodingFromByteOrderMarks: false,
bufferSize: 512, leaveOpen: true);
var requestBody = await reader.ReadToEndAsync();
context.Request.Body.Position = 0;
var requestTelemetry = context.Features.Get<RequestTelemetry>();
requestTelemetry?.Properties.Add("RequestBody", requestBody);
}
await next(context);
}
}
My JWT authentication added user properties into Claims
var claims = new List<Claim>
{
new("UserId", user.Id.ToString()),
new(ClaimTypes.Name, user.DisplayName),
new(ClaimTypes.Email, request.Email),
new(ClaimTypes.AuthenticationMethod, "Password"),
new("LastLoginTimeUtc", user.LastLoginTimeUtc.ToString())
};
Which can be accessed from HttpContext.User
property.
So, to log user information into Application Insights is very straight forward, just get value from Claims and serialize them into JSON.
if (context.User.Identity is { IsAuthenticated: true })
{
var userId = Guid.Parse(context.User.FindFirst(p => p.Type == "UserId")?.Value ?? string.Empty);
var userName = context.User.Identity?.Name;
var email = context.User.FindFirst(p => p.Type == ClaimTypes.Email)?.Value;
requestTelemetry?.Properties.Add("MpsUser", JsonSerializer.Serialize(new
{
userId,
userName,
email
}, MpsJsonSerializerOptions.Default));
}
The final code looks like this
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var method = context.Request.Method;
context.Request.EnableBuffering();
if (context.Request.Body.CanRead && (method == HttpMethods.Post || method == HttpMethods.Put))
{
using var reader = new StreamReader(
context.Request.Body,
Encoding.UTF8,
detectEncodingFromByteOrderMarks: false,
bufferSize: 512, leaveOpen: true);
var requestBody = await reader.ReadToEndAsync();
context.Request.Body.Position = 0;
var requestTelemetry = context.Features.Get<RequestTelemetry>();
requestTelemetry?.Properties.Add("RequestBody", requestBody);
if (context.User.Identity is { IsAuthenticated: true })
{
var userId = Guid.Parse(context.User.FindFirst(p => p.Type == "UserId")?.Value ?? string.Empty);
var userName = context.User.Identity?.Name;
var email = context.User.FindFirst(p => p.Type == ClaimTypes.Email)?.Value;
requestTelemetry?.Properties.Add("MpsUser", JsonSerializer.Serialize(new
{
userId,
userName,
email
}, MpsJsonSerializerOptions.Default));
}
}
await next(context);
}
There's still one place we need to be aware. Because the middleware need to get user information, which makes it must be put after authentication and authorization middleware.
app.UseAuthentication();
app.UseAuthorization();
app.UseRequestBodyLogging();
app.UseResponseBodyLogging();
With these in place, we can now see user identity being logged into Azure Application Insights.
Anduin
Super cool! Exactly what I need!