Handle only controller requests with middleware - http

I have simple asp core middleware
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Web.Identity
{
public class UserContextHttpMiddleware
{
private readonly RequestDelegate _next;
public UserContextHttpMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
Trace.WriteLine(context.Request.Path);
return _next(context);
}
}
}
After refresh page i have this in output
/
/assets/images/avatar-2.jpg
/assets/images/avatar-3.jpg
/assets/images/avatar-6.jpg
/assets/images/avatar-5.jpg
/assets/images/avatar-7.jpg
/assets/images/avatar-8.jpg
/assets/images/avatar-4.jpg
/assets/images/avatar-1.jpg
/images/user-background.png
/assets/images/avatar-10.jpg
/assets/images/avatar-9.jpg
First line is request to controler. Other lines is requests to static content.
So, how can i filter only request that will be handled by controller and ignore request to static content? How can know is current request is request to controller?

Middlewares are executed in the order they are registered (the app.UseXxx calls, see docs).
So if you register your middleware just before the MVC middleware, it should only log these. Be aware that it will also log non-existing routes though.
app.UseMiddleware<UserContextHttpMiddleware>();
app.UseMvc();
Alternatively you can register your middleware to actions, which is a bit more complicated (see Using middleware in the filter pipeline docs).
public class UserContextHttpPipeline
{
public Configure(IApplicationBuilder app)
{
app.UseMiddleware<UserContextHttpMiddleware>();
}
}
Then register it as a global filter
services.AddMvc(options =>
{
options.Filters.Add(new MiddlewareFilter(typeof(UserContextHttpMiddleware2)));
});
This will register the middleware to each action and will be called after Mvc middleware. This should also make sure only valid actions will be logged and invalid routes are not.

Related

How to set default versioning in ASP.NET Core 6 Web API for my scenario?

Just realised that my understanding about ASP.NET Core 6 Web API versioning is wrong.
This is my controller:
[ApiVersion("1.0")]
[ApiController]
[Authorize]
public class FundController
{
[MapToApiVersion("1.0")]
[Route("/Fund/v{version:apiVersion}/delta")]
public async Task<List<PortfolioHolding<Holding>>> Delta([FromQuery] Request dataModel)
{
}
}
What I want is to support route /Fund/v1.0/delta and /Fund/delta, when versioning not provided by the consumer (e.g. calling /Fund/delta), the default version will be hit.
So I configured the versioning like this. However, when I call /Fund/delta, I get a http 404 error.
But /Fund/v1.0/delta will hit the correct controller.
What am I doing wrong?
services.AddApiVersioning(option =>
{
option.DefaultApiVersion = new ApiVersion(1, 0);
option.AssumeDefaultVersionWhenUnspecified = true;
option.ReportApiVersions = true;
});
Usually, it's pretty easy to do this that way. The disadvantage of this approach is that you need to manually change the "default" version of API with this attribute
The problem is that you have not specified the routes in the controller.
You should add the default route as well as the formatted version route. Then you should ensure that your endpoints have the version specified in the MapToApiVersion attribute.
Here is a code sample of what your controller should look like:
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[Route("[controller]")]
[Route("[controller]/v{version:apiVersion}")]
public class FundController : ControllerBase
{
[MapToApiVersion("1.0")]
[Route("delta")]
[HttpGet]
public async Task<List<PortfolioHolding<Holding>>> DeltaV1([FromQuery] Request dataModel)
{
}
[MapToApiVersion("2.0")]
[Route("delta")]
[HttpGet]
public async Task<List<PortfolioHolding<Holding>>> DeltaV2([FromQuery]
Request dataModel)
{
}
}

Scope in Middleware and Blazor Component

I'm working on a server-side Blazor application and ran into some problems regarding a scoped service.
For simplicity's sake I have re-created my issue using the default Blazor template (the one with the counter).
I have a service "CounterService", which initializes a counter to 1 and exposes this counter together with a method to increment it. Really basic:
public class CounterService
{
public int Counter { get; private set; }
public CounterService()
{
Counter = 1;
}
public void IncrementCounter()
{
Counter++;
}
}
I have then registered this counter in my Startup.cs as a scoped service: `services.AddScoped()
Then I have a custom ASP.NET middleware, in this case a "CounterInitializerMiddleware".
public class CounterInitializerMiddleware
{
public CounterInitializerMiddleware(RequestDelegate next)
{
_next = next;
}
public RequestDelegate _next { get; }
public async Task Invoke(HttpContext context, CounterService counterService)
{
Console.WriteLine($"CounterInitializer invoked from request path: {context.Request.Path.Value}");
counterService.IncrementCounter();
counterService.IncrementCounter();
await _next(context);
}
}
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseCounterInitializer(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<CounterInitializerMiddleware>();
}
}
Basically, its a middle layer to increment the counter so that it starts at 3 rather than 1 when I get the service injected to my component(s). I register it in my Configure-method in Startup.cs: `app.UseCounterInitializer();
This middleware-layer is invoked 4 times when I start up my application (note that it has RenderMode set to ServerPreRendered): At the page-load request and at the _blazor-requests:
CounterInitializer invoked from request path: /counter
CounterInitializer invoked from request path: /_blazor/disconnect
CounterInitializer invoked from request path: /_blazor/negotiate
CounterInitializer invoked from request path: /_blazor
The scoped service is injected, and all seems good.
Then, if I have a component with the CounterService injected, it seems the scopes get messed up.
If I look at the OnInitialized-method, this is called twice. Once during the pre-render and once during normal render.
At the pre-render execution, the CounterService has Counter set to 3 as expected, since it has been through the CounterInitializerMiddleware. However, during render execution, the CounterService is spawned fresh. So it seems the scope of the normal render and the scope(s) of the requests going through the middleware are different. I thought the scope of the components would be bound to the "_blazor"-signalR connection which is processed my the middleware.
Anyone who can figure out what is going on and help me understand how to accomplish what I'm trying to do?
Best,
Mathias
EDIT: Just to clarify. My real use-case is something entirely different, and the Counter-example is just a simplified case showcasing the issue and is more easily reproducible (I hope).
I've hit the same problem and needed a quick workaround.
The workaround is to get the service from the HttpContext, which is an anti-pattern but better than nothing.
class YourClass
{
private readonly SomeMiddlewareScopedService _service;
public YourClass(SomeMiddlewareScopedServiceservice)
{
_service = service;
}
}
The workaround:
class YourClass
{
private readonly SomeMiddlewareScopedService _service;
public YourClass(IHttpContextAccessor contextAccessor)
{
_service= (SomeMiddlewareScopedService)contextAccessor.HttpContext.RequestServices.GetService(typeof(SomeMiddlewareScopedService));
}
}
Don't forget to add to your builder:
builder.Services.AddHttpContextAccessor();

Exclude Controller from Middleware

I have wrote a Middleware which checks if Authorization Token is included in the header and based on that request are executed or returns error if token is missing. Now it is working fine for other Controllers.
But What should I do for Login/Registration Controller which don't required Authorization headers. How can I configure my Middleware to ignore these.
Current Implementation of MiddleWare to Check Headers for Authorization Token.
public class AuthorizationHeaderValidator
{
private readonly RequestDelegate _next;
private readonly ILogger<AuthorizationHeaderValidator> _logger;
public AuthorizationHeaderValidator(RequestDelegate next, ILogger<AuthorizationHeaderValidator> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
StringValues authorizationHeader;
Console.WriteLine(context.Request.Path.Value);
if (context.Request.Headers.TryGetValue("Authorization", out authorizationHeader))
{
await _next(context);
}
else
{
_logger.LogError("Request Failed: Authorization Header missing!!!");
context.Response.StatusCode = 403;
var failureResponse = new FailureResponseModel()
{
Result = false,
ResultDetails = "Authorization header not present in request",
Uri = context.Request.Path.ToUriComponent().ToString(),
Timestamp = DateTime.Now.ToString("s", CultureInfo.InvariantCulture),
Error = new Error()
{
Code = 108,
Description = "Authorization header not present in request",
Resolve = "Send Request with authorization header to avoid this error."
}
};
string responseString = JsonConvert.SerializeObject(failureResponse);
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(responseString);
return;
}
}
}
This is not a complete answer but only directions. Please post your code once you finish this task for next generations.
It seems you need a Filter and not Middlware as Middleware don't have access to rout data. Create new authorization filter by inheriting from Attribute and implementing IAuthorizationFilter or IAsyncAuthorizationFilter. There is only one method to implement
public void OnAuthorization(AuthorizationFilterContext context)
{
}
or
public Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
}
Decorate controllers and/or actions that you want to exclude from this logic with AllowAnonymousAttribute. Inside your OnAuthorization method check if current action or controller has AllowAnonymousAttribute and if it is return without setting Result on AuthorizationFilterContext. Otherwise execute the logic from you original Middleware and set Result property. Setting Result will short-circuit the remainder of the filter pipeline.
Then register your filter globally:
services.AddMvc(options =>
{
options.Filters.Add(new CustomAuthorizeFilter());
});
Not sure why you need middleware to validate if the Authorization header is present. It's difficult to exclude the controllers this way as all requests will go through this middleware before they hit the MVC pipeline.
[Authorize] attribute will do the job for you, given that you have some form of authentication integrated. If you need to exclude the controllers which don't require authorization, you can simply add [AllowAnonymous] at the controller level or at the action method level. Please see the code snippet below from the Microsoft Docs
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login()
{
}
public ActionResult Logout()
{
}
}
If you must use a middleware, you can consider using it as an MVC filter, which means that it will be scoped to the MVC pipeline. For more details, please see this link. However, that will still not solve the problem to exclude the controllers without adding some convoluted logic, which can be quite complicated.
I have solved my problem by Implementing PipeLine
public class AuthorizationMiddlewarePipeline
{
public void Configure(IApplicationBuilder applicationBuilder)
{
applicationBuilder.UseMiddleware<AuthorizationHeaderValidator>();
}
}
And than I am using it like this on either Controller Scope or Method scope
[MiddlewareFilter(typeof(AuthorizationMiddlewarePipeline))]

FeignClients get published as REST endpoints in spring cloud application

I've got REST FeignClient defined in my application:
#FeignClient(name = "gateway", configuration = FeignAuthConfig.class)
public interface AccountsClient extends Accounts {
}
I share endpoint interface between server and client:
#RequestMapping(API_PATH)
public interface Accounts {
#PostMapping(path = "/register",
produces = APPLICATION_JSON_VALUE,
consumes = APPLICATION_JSON_VALUE)
ResponseEntity<?> registerAccount(#RequestBody ManagedPassUserVM managedUserDTO)
throws EmailAlreadyInUseException, UsernameAlreadyInUseException, URISyntaxException;
}
Everythng works fine except that my FeignClient definition in my client application also got registered as independent REST endpoint.
At the moment I try to prevent this behavior using filter which returns 404 status code for FeignClinet client mappings in my client application. However this workeraund seems very inelegant.
Is there another way how to prevent feign clients registering as separate REST endpoints?
It is a known limitation of Spring Cloud's feign support. By adding #RequestMapping to the interface, Spring MVC (not Spring Cloud) assumes you want as an endpoint. #RequestMapping on Feign interfaces is not currently supported.
I've used workaround for this faulty Spring Framework behavior:
#Configuration
#ConditionalOnClass({Feign.class})
public class FeignMappingDefaultConfiguration {
#Bean
public WebMvcRegistrations feignWebRegistrations() {
return new WebMvcRegistrationsAdapter() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new FeignFilterRequestMappingHandlerMapping();
}
};
}
private static class FeignFilterRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
#Override
protected boolean isHandler(Class<?> beanType) {
return super.isHandler(beanType) && (AnnotationUtils.findAnnotation(beanType, FeignClient.class) == null);
}
}
}
I found it in SpringCloud issue

In ASP.NET 5, how do I get the chosen route in middleware?

I am building an ASP.NET 5 (vNext) site that will host dynamic pages, static content, and a REST Web API. I have found examples of how to create middleware using the new ASP.NET way of doing things but I hit a snag.
I am trying write my own authentication middleware. I would like to create a custom attribute to attach to the controller actions (or whole controllers) that specifies that it requires authentication. Then during a request, in my middleware, I would like to cross reference the list of actions that require authentication with the action that applies to this current request. It is my understanding that I configure my middleware before the MVC middleware so that it is called first in the pipeline. I need to do this so the authentication is done before the request is handled by the MVC controller so that I can't prevent the controller from ever being called if necessary. But doesn't this also mean that the MVC router hasn't determined my route yet? It appears to me the determination of the route and the execution of that routes action happen at one step in the pipeline right?
If I want to be able to determine if a request matches a controller's action in a middleware pipeline step that happens before the request is handled by the controller, am I going to have to write my own url parser to figure that out? Is there some way to get at the routing data for the request before it is actually handled by the controller?
Edit: I'm beginning to think that the RouterMiddleware might be the answer I'm looking for. I'm assuming I can figure out how to have my router pick up the same routes that the standard MVC router is using (I use attribute routing) and have my router (really authenticator) mark the request as not handled when it succeeds authentication so that the default mvc router does the actual request handling. I really don't want to fully implement all of what the MVC middleware is doing. Working on trying to figure it out. RouterMiddleware kind of shows me what I need to do I think.
Edit 2: Here is a template for the middleware in ASP.NET 5
public class TokenAuthentication
{
private readonly RequestDelegate _next;
public TokenAuthentication(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
//do stuff here
//let next thing in the pipeline go
await _next(context);
//do exit code
}
}
I ended up looking through the ASP.NET source code (because it is open source now!) and found that I could copy the UseMvc extension method from this class and swap out the default handler for my own.
public static class TokenAuthenticationExtensions
{
public static IApplicationBuilder UseTokenAuthentication(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
var routes = new RouteBuilder
{
DefaultHandler = new TokenRouteHandler(),
ServiceProvider = app.ApplicationServices
};
configureRoutes(routes);
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(
routes.DefaultHandler,
app.ApplicationServices));
return app.UseRouter(routes.Build());
}
}
Then you create your own version of this class. In my case I don't actually want to invoke the actions. I will let the typical Mvc middleware do that. Since that is the case I gut all the related code and kept just what I needed to get the route data which is in actionDescriptor variable. I probably can remove the code dealing with backing up the route data since I dont think what I will be doing will affect the data, but I have kept it in the example. This is the skeleton of what I will start with based on the mvc route handler.
public class TokenRouteHandler : IRouter
{
private IActionSelector _actionSelector;
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
EnsureServices(context.Context);
context.IsBound = _actionSelector.HasValidAction(context);
return null;
}
public async Task RouteAsync(RouteContext context)
{
var services = context.HttpContext.RequestServices;
EnsureServices(context.HttpContext);
var actionDescriptor = await _actionSelector.SelectAsync(context);
if (actionDescriptor == null)
{
return;
}
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
if (actionDescriptor.RouteValueDefaults != null)
{
foreach (var kvp in actionDescriptor.RouteValueDefaults)
{
if (!newRouteData.Values.ContainsKey(kvp.Key))
{
newRouteData.Values.Add(kvp.Key, kvp.Value);
}
}
}
try
{
context.RouteData = newRouteData;
//Authentication code will go here <-----------
var authenticated = true;
if (!authenticated)
{
context.IsHandled = true;
}
}
finally
{
if (!context.IsHandled)
{
context.RouteData = oldRouteData;
}
}
}
private void EnsureServices(HttpContext context)
{
if (_actionSelector == null)
{
_actionSelector = context.RequestServices.GetRequiredService<IActionSelector>();
}
}
}
And finally, in the Startup.cs file's Configure method at the end of the pipeline I have it setup so that I use the same routing setup (I use attribute routing) for the both my token authentication and mvc router.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//Other middleware delcartions here <----------------
Action<IRouteBuilder> routeBuilder = routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
};
app.UseTokenAuthentication(routeBuilder);
//Middleware after this point will be blocked if authentication fails by having the TokenRouteHandler setting context.IsHandled to true
app.UseMvc(routeBuilder);
}
Edit 1:
I should also note that at the moment I am not concerned about the extra time required to select the route twice which is what I think would happen here since both my middleware and the Mvc middleware will be doing that. If that becomes a performance problem then I will build the mvc and authentication in to one handler. That would be best idea performance-wise, but what I have shown here is the most modular approach I think.
Edit 2:
In the end to get the information I needed I had to cast the ActionDescriptor to a ControllerActionDescriptor. I am not sure what other types of actions you can have in ASP.NET but I am pretty sure all my action descriptors should be ControllerActionDescriptors. Maybe the old legacy Web Api stuff needs another type of ActionDescriptor.

Resources