Log custom web request headers with NLog - asp.net-web-api2

I'm trying to log request number in my Web Api application adding it as custom request header on the first DelegatingHandler:
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add(WebApiConstants.Headers.WebRequestCounterHeader, Interlocked.Increment(ref _requestsCounter).ToString("X8"));
return base.SendAsync(request, cancellationToken);
}
Where:
public const string WebRequestCounterHeader = "RequestIndex";
Part of my NLog configuration presented below:
<variable name="RequestNumber"
value="${aspnet-request:serverVariable=HEADER_RequestIndex}" />
<target name="TraceLogger"
type="File"
fileName="${TraceFullPath}"
archiveFileName="${TraceArchiveFullPath}"
maxArchiveFiles="100"
archiveNumbering="Sequence"
archiveEvery="Day"
layout="${FormattedDate} ${message}${RequestNumber} -> ${HttpMethodPadded} ${Url}" />
But my logged messages doesn't contain a request number:
26.10.2015 18:11:22.11 Begin -> [POST] /api/v1/login
According to MSDN and NLog help:
HEADER_
Unlike HTTP_, all characters in HEADER_ are interpreted as-is. For example, if you specify HEADER_MY_HEADER, the server searches for a request header named MY_HEADER.
What NLog configuration will be correct in my case?

Related

asp.net core favicon.ico goes through custom middleware

In my CustomMiddleware, I have a simple logging and some Authenticated user related code.
It seems like favicon.ico request goes through CustomMiddleware, but request path is "/" same as index page page. can not differentiate.
If I open up a link like this - https://localhost:5001/favicon.ico, it does not hit my debug point.
I need help to understand why first time ONLY request "/", it goes through CustomMiddleware ???
In the CustomMiddleware, first two request path "/" (one is Index), IsAuthenticated is false.
after that, it is always true as it goes through OIDC authentication.
You could read the offcial document:https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0
The ASP.NET Core request pipeline consists of a sequence of request delegates, called one after the other.
Each delegate can perform operations before and after the next delegate. Exception-handling delegates should be called early in the pipeline, so they can catch exceptions that occur in later stages of the pipeline.
When a delegate doesn't pass a request to the next delegate, it's called short-circuiting the request pipeline. Short-circuiting is often desirable because it avoids unnecessary work. For example, Static File Middleware can act as a terminal middleware by processing a request for a static file and short-circuiting the rest of the pipeline.
You could write two custommiddle and understand how middlewareworks
public class MyCustomMiddleWare
{
private RequestDelegate _next;
public MyCustomMiddleWare(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var pathstring = context.Request.Path.ToString();
var pathlist = pathstring.Split("/").ToList();
if (pathlist[1]=="")
{
await _next.Invoke(context);
}
else
{
await context.Response.WriteAsync("Result");
}
}
}
public class MyAnotherCustomMiddleWare
{
private RequestDelegate _next;
public MyAnotherCustomMiddleWare(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
await _next.Invoke(context);
}
}
in startupclass:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.......
app.UseStaticFiles();
app.UseMiddleware<MyCustomMiddleWare>();
app.UseMiddleware<MyAnotherCustomMiddleWare>();
.......
}
Test Result:
If you open up a link of staticfile and the request hit your custommiddleware behind UseStaticFile Middleware,check if the static file exists.
(Has the BuildAction property of file set as "content"?and check the codes in csproj related which update content files)
The order of the Middlewares is very important and they run from top to bottom They can receive the request, process it, and pass that request to the next Middleware, or not. When the file request like https://localhost:5001/favicon.ico reaches the UseStaticFile Middleware, it processes that request and no longer sends it to its lower Middleware
But when the request is not a file request like https://localhost:5001/, the UseStaticFile Middleware receives the request and passes it to the next middleware.
This is why the request does not reach your custom Middleware. If you want it to be called, you must register your Middleware before the UseStaticFile Middleware like this :
app.UseMiddleware<CustomMiddlware>();
app.UseStaticFiles();
You only need to pay attention to one point: static files like css and ... are cached by the browser after first request. If you request them again, your request will not reach your APP and will be processed by the browser.

Why does retrieving an invalid endpoint in .NET 6 through the HttpContext for a GET and a POST return different result?

I have a .NET 6 middleware that checks a few things and in it I am trying to validate whether the request provided has a proper route or not.
I have the following in my middleware
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
// This returns null if it's a GET to an invalid route.
// But it returns method not supported if it's a POST/PUT
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
}
Why is .NET 6 returning two different values for this and is there a better way to identify early on in a middleware that a request does not have the correct route?
From source code of EndpointMiddleware.cs, the middleware from app.UseEndpoints(endpoints => [...]);
To get an endpoint object in a middleware you have to use this method :
public Task Invoke(HttpContext httpContext)
{
var endpoint = httpContext.GetEndpoint();
[...]
}
The endpoint variable will be null if the requested route doesn't match any known routes.
In startup.cs you have to add your middleware just after app.UseRouting() which is responsible of making the match.

MobileFirst 8: get client data ( IP address, request data ) in UserAuthenticationSecurityCheck

I'm trying to get some client data inside the UserAuthenticationSecurityCheck.validateCredentials method.
The IP Address is the most important for it.
In the other adapters, I'm using the HttpServletRequest:
#Context
protected HttpServletRequest request;
But this request object is always null in the UserAuthenticationSecurityCheck.
How can I get client data (IP Address or the headers) in this class?
You cannot inject the HttpServletRequest into a security check object(by design - not a bug). Once the user is authenticated, then you can make another Adapter Call, from where you can get the desired details. Unfortunately this is not documented anywhere (not to my knowledge at least).
I had a similar issue with AdapterAPI class as described here.
You can get request in security adapter but not from #Context.
Just override authorize method:
#Override
public void authorize(Set<String> scope, Map<String, Object> credentials, HttpServletRequest request, AuthorizationResponse response) {
//TODO use request object
super.authorize(scope, credentials, request, response);
}

How to get base url without accessing a request

How to get the base URL in AspNet core application without having a request?
I know from the Request you can get the scheme and host (ie $"{Request.Scheme}://{Request.Host}" would give something like https://localhost:5000), but is it possible to get this information from anywhere else?
In other words, if I have a service class that needs to build absolute URLs, how can I get the current URL when there is not an http request available?
UPDATE: Maybe that scenario does not even make sense since the hosting URL is totally external to the application and that's why it only makes sense to extract it from the Request host..
i needed for some reason to get the base URL in Start.cs Configure, so i come up with this
var URLS = app.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
You are right, hosting URL is an external information, and you can simply pass it as configuration parameter to your application.
Maybe this will help you somehow: without request, you can get a configured listening address (like http://+:5000) using the IWebHostBuilder interface. It provides access to host settings via the GetSetting method:
/// <summary>
/// Get the setting value from the configuration.
/// </summary>
/// <param name="key">The key of the setting to look up.</param>
/// <returns>The value the setting currently contains.</returns>
string GetSetting(string key);
There is a WebHostDefaults.ServerUrlsKey setting name, that allows to configure listening address. We override it when add .UseUrls extension method:
public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls);
or define urls configuration parameter as described in the documentation (you know, by default listening is configured to localhost:5000).
So, having instance of IWebHostBuilder, you can call .GetSetting(WebHostDefaults.ServerUrlsKey) and get the current value.
,The ASP.NET Core Module generates a dynamic port to assign to the backend process. CreateDefaultBuilder calls the UseIISIntegration method. UseIISIntegration configures Kestrel to listen on the dynamic port at the localhost IP address (127.0.0.1). If the dynamic port is 1234, Kestrel listens at 127.0.0.1:1234. This configuration replaces other URL configurations provided by.
For IIS Integration, it works if you get the address after the WebHostBuilder.Build() have run.
var builder = CreateWebHostBuilder(args);
var webHost = builder.Build();
var addresses = webHost.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
var address = addresses.FirstOrDefault();
AppDomain.CurrentDomain.SetData("BaseUrl", address ?? "");
webHost.Run();
and got the local Kestrel address in the HostedService like this:
string baseUrl = AppDomain.CurrentDomain.GetData("BaseUrl").ToString();
But there's a catch - this address is useless, because you can not make a request directly on this address. The IIS Integration middleware checks that only the IIS handler can make a request on this address. It produces a similar error:
<category>Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware</category>
<state>'MS-ASPNETCORE-TOKEN' does not match the expected pairing token 'ed5bc610-b7b9-4c1c-9941-954d0579edfc', request rejected.</state>
And in general case (no IIS Integration) this method of getting the address does not work if you use Kestrel configured to run with a custom port (not 5000), or a dynamic port 0. In this case the address needs to be obtained in a delayed manner, only after the application started.
For this case i tried this way: In Configure method in the StartUp class, i saved in ServerAddressFeature in the private member.
private IServerAddressesFeature _serverAddressesFeature;
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
_serverAddressesFeature = app.ServerFeatures.Get<IServerAddressesFeature>();
... not related code here ...
And in the ConfigureServices method i added a dependency
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServerAddressesFeature>((sp) => _serverAddressesFeature);
... not related code here ...
Then in a hosted service i obtain this saved feature using dependency injection, and use it to get the address.
It works, only get the address in the StartAsync method, not in the service constructor!
public class WarmUpService : IHostedService
{
private readonly ILogger _logger;
private readonly IServerAddressesFeature _saf;
public WarmUpService(ILogger<WarmUpService> logger, IServerAddressesFeature serverAddressesFeature)
{
_logger = logger;
_saf = serverAddressesFeature;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
try
{
// the URL can be Got here
string baseUrl = _saf?.Addresses?.FirstOrDefault();
// await _WarmUp(baseUrl);
}
catch(Exception ex)
{
_logger.LogCritical(ex, "WarmUp Failed");
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}

Spring Security: forward to original page after login with admin user after access was denied

I use Spring security to authenticate users. If a user requests a secured page, he has to authenticate over a login page. If the user is always authenticated, he will be redirected to the requested page immediatly. Moreover some pages need special access rights, and so I setup an access-denied-page temporarily. So far so good.
The scenario:
The scenario definies, that the user will get a login-form instead of a static access-denied page, so that a different user can authenticate and if authentication is successful the requested page that needs the higher privileges will open.
The actual spring configuration reads:
<security:http auto-config="true" use-expressions="true" disable-url-rewriting="true">
<security:intercept-url pattern="/index.jsp" access="permitAll" />
<security:intercept-url pattern="/loginView" access="permitAll" />
<security:intercept-url pattern="/accessDenied" access="permitAll"/>
<security:intercept-url pattern="/user" access="hasRole('ROLE_USER')" />
<security:intercept-url pattern="/admin" access="hasRole('ROLE_ADMIN')" />
<security:intercept-url pattern="/**" access="denyAll"/>
<security:form-login login-page="/loginView"
authentication-failure-url="/loginView"
default-target-url="/dirView" />
<security:logout />
<security:access-denied-handler ref="accessDeniedHandler" />
</security:http>
The accessDeniedHandler-Bean:
public class AccessDeniedServletRequestHandler implements AccessDeniedHandler {
/** {#inheritDoc} */
#Override
public void handle(HttpServletRequest req, HttpServletResponse resp,
AccessDeniedException accessDeniedException) throws IOException,
ServletException {
RequestDispatcher d = req.getRequestDispatcher("/loginView");
d.forward(req, resp);
}
}
But that implementation of AccessDeniedHandler only forwards to the loginView. After authentication of an admin the default-success-page is openend and not the original requested page. I also tried to save the original request by calling HttpServletRequest#getAttribute("javax.servlet.forward.servlet_path"), but I don't understand how to force spring security to use that original request instead of the default target url.
Besides I read about org.springframework.security.web.savedrequest.SavedRequest that is used inside spring authentication to remember the original request if an unauthenticated user requests a page. But I don't find a valid way how to use the SavedRequest in the same manner for my access denied scenario.
Thanks in advance for suggestions and solutions.
I think your requirements should be satisfied by using the RequestCache API.
If you modify your http configuration you can use the request-cache namespace element:
<security:http>
...
<security:request-cache ref="requestCache"
</security:http>
<bean id="requestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache" />
You can also inject it into your AccessDeniedHandler. Then all you should need is a simple to saveRequest to setup the cached request which should be restored post-authentication:
public class AccessDeniedServletRequestHandler implements AccessDeniedHandler {
// Inject this into your class.
private RequestCache requestCache;
#Override
public void handle(HttpServletRequest req, HttpServletResponse resp,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
requestCache.saveRequest(req, resp);
RequestDispatcher d = req.getRequestDispatcher("/loginView");
d.forward(req, resp);
}
}
Strictly speaking, you don't actually need to do the bit with the namespace at all, since HttpSessionRequestCache is stateless (it's the internal implementation which is used if you don't override it in the namespace). So you could just create one directly in your AccessDeniedHandler class and it would still work.