Elegant way to use Multiple SwaggerResponses - c#

I gonna use these attributes on my controller:
[SwaggerResponse((int)HttpStatusCode.OK, typeof(GetAutotopupConfigurationResponse))]
[SwaggerResponse((int)HttpStatusCode.BadRequest, typeof(ErrorResponse), BadRequestMessage)]
[SwaggerResponse((int)HttpStatusCode.Unauthorized, typeof(ErrorResponse), InvalidCredentiasMessage)]
[SwaggerResponse((int)HttpStatusCode.Forbidden, typeof(ErrorResponse), UserNoRightsMessage)]
[SwaggerResponse((int)HttpStatusCode.NotFound, typeof(ErrorResponse), AutopopupNotFoundMessage)]
[SwaggerResponse((int)HttpStatusCode.InternalServerError, typeof(ErrorResponse), InternalServerErrorMessage)]
How do I simplify the logic and reduce code ammount or make it more flexible somehow?

EDIT This answer applies to Asp.Net-Core but may be useful for this question too.
If you're using Swashbuckle you can use an IOperationFilter and Reflection to target specific endpoints and programmatically apply the responses.
It's possible to use an IOperationFilter to apply InternalServerError to all endpoints in your service. Below is an example:
public class ServerErrorResponseOperationFilter : IOperationFilter
{
// Applies the specified operation. Adds 500 ServerError to Swagger documentation for all endpoints
public void Apply(Operation operation, OperationFilterContext context)
{
// ensure we are filtering on controllers
if (context.MethodInfo.DeclaringType.BaseType.BaseType == typeof(ControllerBase)
|| context.MethodInfo.ReflectedType.BaseType == typeof(Controller))
{
operation.Responses.Add("500", new Response { Description = "Server Error" });
}
}
}
You need to set Swagger to use these filters. You can do so by adding in the setup:
services.AddSwaggerGen(swag =>
{
swag.SwaggerDoc("v1", new Info { Title = "Docs", Version = "v1" });
// add swagger filters to document default responses
swag.OperationFilter<ServerErrorResponseOperationFilter>();
});
You can use other filters to apply 401 Unauthorized, 403 Forbidden, etc. You can even use Reflection to add 201 Created for actions decorated with [HttpPost] and you could do something similar for other Http attributes.
If you have filters for 401, 403 and 500 that will tidy up your controller slightly. You will still need to add attributes for certain methods that can't be dealt with by Reflection. With this method I find I only need to add one or 2 attributes, typically [ProcudesResponseType((int)HttpStatusCode.BadRequest)] and [ProcudesResponseType((int)HttpStatusCode.NotFound)].

Related

Logging automatic HTTP 400 responses in .NET Core 6 API

[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status303SeeOther)]
[HttpPost]
[Route("RegisterUsers")]
public async Task<ActionResult<List<UsersInfo>>> RegisterUsers(List<UsersInfo> Users)
{
// .. how to detect errors here ...
return Users;
}
How do I get errors here specially when API receive wrong format for UserInfo type in the body?
The method implementation never run in the case of wrong userinfo type.
It depends either it's a Web API or ASP MVC.
Assuming you have Web API, The [ApiController] attribute makes model validation errors automatically trigger an HTTP 400 response.
Consequently, the following code is unnecessary in an action method:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
The default response type for an HTTP 400 response is ValidationProblemDetails.
In case you need to log such automated responses, you can set the InvalidModelStateResponseFactory to a custom function that first performs the logging and then returns an appropriate BadRequestObjectResult
As an example, you can try to do it like this (see original documentation)
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// To preserve the default behaviour, capture the original delegate to call later.
var builtInFactory = options.InvalidModelStateResponseFactory;
options.InvalidModelStateResponseFactory = context =>
{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<Program>>();
// Perform logging here.
// ...
// Invoke the default behaviour, which produces a ValidationProblemDetails
// response.
// To produce a custom response, return a different implementation of
// IActionResult instead.
return builtInFactory(context);
};
});
Well, there are a lot of things you are able to implement. In your case I think you need a kind of Validator to your Endpoint parameters. I suggest to implement something like FluentValidation, so the sender will receive all model errors (if any).
Also you can customize the response in that case.
Here you can find more about it
https://docs.fluentvalidation.net/en/latest/

How does Consumes filter work with action selection?

I saw some code like this:
[HttpPost]
[Consumes("application/json")]
public string SaveProductJson(ProductBindingTarget product) {
return $"JSON: {product.Name}";
}
[HttpPost]
[Consumes("application/xml")]
public string SaveProductXml(ProductBindingTarget product) {
return $"XML: {product.Name}";
}
I get the idea of how Consumes filter work, but a little bit confused about how it work internally. Below is the picture from MSDN:
From my understanding, the routing middleware will select the matching action method. Let's say I post a json document to the application, so both SaveProductJson and SaveProductXml match the request (because they have the same routing template [HttpPost]) and Consumes filter hasn't kicked in yet (filters run in endpoint middleware), since Consumes filter runs after routing middleware, how does Consumes filter tell the routing middleware to select SaveProductJson action method?
The Consumes attribute works together with Content-Type header. In your case application/json or application/xml, but it also supports other content types such as application/x-www-form-urlencoded if you want to submit a form.
The Consumes attribute allows an action to limit the supported request content types. Apply the Consumes attribute to an action or controller, specifying one or more content type.
Read more here
The ConsumeAttribute class inherits from IResourceFilter, which is an extensible hook in the MVC pipeline that is called from Controller/Page implementations of ResourceInvoker.
When the ResourceInvoker is executing during the middleware pipeline, it calls the OnResourceExecuting method of the ConsumesAttribute if it has been previously discovered on target actions.
This method then checks the incoming Content-Type header of the request and compares it
public void OnResourceExecuting(ResourceExecutingContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// Only execute if the current filter is the one which is closest to the action.
// Ignore all other filters. This is to ensure we have a overriding behavior.
if (IsApplicable(context.ActionDescriptor))
{
var requestContentType = context.HttpContext.Request.ContentType;
// Confirm the request's content type is more specific than a media type this action supports e.g. OK
// if client sent "text/plain" data and this action supports "text/*".
//
// Requests without a content type do not return a 415. It is a common pattern to place [Consumes] on
// a controller and have GET actions
if (!string.IsNullOrEmpty(requestContentType) && !IsSubsetOfAnyContentType(requestContentType))
{
context.Result = new UnsupportedMediaTypeResult();
}
}
}
In addition, the attribute also inherits from IActionConstraint which will call the Accept method of targetable constraints in the ActionConstraintMatcherPolicy. This component determines the appropriate target action, based on the matching policies against the content-type which is provided by the ConsumeAttribute.Accept method.

How to correctly setup Policy Authorization for WEB API in .NET Core

I have this Web API project, with no UI. My appsettings.json file has a section listing tokens and which client they belong to. So the client will need to just present a matching token in the header. If no token is presented or an invalid one, then it should be returning a 401.
In ConfigureServices I setup authorization
.AddTransient<IAuthorizationRequirement, ClientTokenRequirement>()
.AddAuthorization(opts => opts.AddPolicy(SecurityTokenPolicy, policy =>
{
var sp = services.BuildServiceProvider();
policy.Requirements.Add(sp.GetService<IAuthorizationRequirement>());
}))
This part fires correctly from what I can see.
Here is code for the ClientTokenRequirement
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClientTokenRequirement requirement)
{
if (context.Resource is AuthorizationFilterContext authFilterContext)
{
if (string.IsNullOrWhiteSpace(_tokenName))
throw new UnauthorizedAccessException("Token not provided");
var httpContext = authFilterContext.HttpContext;
if (!httpContext.Request.Headers.TryGetValue(_tokenName, out var tokenValues))
return Task.CompletedTask;
var tokenValueFromHeader = tokenValues.FirstOrDefault();
var matchedToken = _tokens.FirstOrDefault(t => t.Token == tokenValueFromHeader);
if (matchedToken != null)
{
httpContext.Succeed(requirement);
}
}
return Task.CompletedTask;
}
When we are in the ClientTokenRequirement and have not matched a token it returns
return Task.CompletedTask;
This is done how it is documented at
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1
This works correctly when there is a valid token, but when there isnt and it returns Task.Completed, there is no 401 but an exception instead
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
I have read other stackoverflow articles about using Authentication rather than Authorization, but really this policy Authorization is the better fit for purpose. So I am looking for ideas on how to prevent this exception.
Interestingly, I think this is just authentication, without any authorisation (at least not in your question). You certainly want to authenticate the client but you don't appear to have any authorisation requirements. Authentication is the process of determining who is making this request and authorisation is the process of determining what said requester can do once we know who it is (more here). You've indicated that you want to return a 401 (bad credentials) rather than a 403 (unauthorised), which I believe highlights the difference (more here).
In order to use your own authentication logic in ASP.NET Core, you can write your own AuthenticationHandler, which is responsible for taking a request and determining the User. Here's an example for your situation:
public class ClientTokenHandler : AuthenticationHandler<ClientTokenOptions>
{
private readonly string[] _clientTokens;
public ClientTokenHandler(IOptionsMonitor<ClientTokenOptions> optionsMonitor,
ILoggerFactory loggerFactory, UrlEncoder urlEncoder, ISystemClock systemClock,
IConfiguration config)
: base(optionsMonitor, loggerFactory, urlEncoder, systemClock)
{
_clientTokens = config.GetSection("ClientTokens").Get<string[]>();
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var tokenHeaderValue = (string)Request.Headers["X-TOKEN"];
if (string.IsNullOrWhiteSpace(tokenHeaderValue))
return Task.FromResult(AuthenticateResult.NoResult());
if (!_clientTokens.Contains(tokenHeaderValue))
return Task.FromResult(AuthenticateResult.Fail("Unknown Client"));
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(
Enumerable.Empty<Claim>(),
Scheme.Name));
var authenticationTicket = new AuthenticationTicket(claimsPrincipal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(authenticationTicket));
}
}
Here's a description of what's going on in HandleAuthenticateAsync:
The header X-TOKEN is retrieved from the request. If this is invalid, we indicate that we are unable to authenticate the request (more on this later).
The value retrieved from the X-TOKEN header is compared against a known list of client-tokens. If this is unsuccessful, we indicate that authentication failed (we don't know who this is - more on this later too).
When a client-token matches the X-TOKEN request header, we create a new AuthenticationTicket/ClaimsPrincipal/ClaimsIdentity combo. This is our representation of the User - you can include your own Claims instead of using Enumerable.Empty<Claim>() if you want to associate additional information with the client.
You should be able to use this as-is for the most part, with a few changes (I've simplified to both keep the answer short and fill in a few gaps from the question):
The constructor takes an instance of IConfiguration as the final parameter, which is then used to read a string[] from, in my example, appsettings.json. You are likely doing this differently, so you can just use DI to inject whatever it is you're currently using here, as needed.
I've hardcoded X-TOKEN as the header name to use when extracting the token. You'll likely be using a different name for this yourself and I can see from your question that you're not hardcoding it, which is better.
One other thing to note about this implementation is the use of both AuthenticateResult.NoResult() and AuthenticateResult.Fail(...). The former indicates that we did not have enough information in order to perform the authentication and the latter indicates that we had everything we needed but the authentication failed. For a simple setup like yours, I think you'd be OK using Fail in both cases if you'd prefer.
The second thing you'll need is the ClientTokenOptions class, which is used above in AuthenticationHandler<ClientTokenOptions>. For this example, this is a one-liner:
public class ClientTokenOptions : AuthenticationSchemeOptions { }
This is used for configuring your AuthenticationHandler - feel free to move some of the configuration into here (e.g. the _clientTokens from above). It also depends on how configurable and reusable you want this to be - as another example, you could define the header name in here, but that's up to you.
Lastly, to use your ClientTokenHandler, you'll need to add the following to ConfigureServices:
services.AddAuthentication("ClientToken")
.AddScheme<ClientTokenOptions, ClientTokenHandler>("ClientToken", _ => { });
Here, we're just registering ClientTokenHandler as an AuthenticationHandler under our own custom ClientToken scheme. I wouldn't hardcode "ClientToken" here like this, but, again, this is just a simplification. The funky _ => { } at the end is a callback that is given an instance of ClientTokenOptions to modify: we don't need that here, so it's just an empty lambda, effectively.
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
The "DefaultChallengeScheme" in your error message has now been set with the call to services.AddAuthentication("ClientToken") above ("ClientToken" is the scheme name).
If you want to go with this approach, you'll need to remove your ClientTokenRequirement stuff. You might also find it interesting to have a look through Barry Dorrans's BasicAuthentication project - it follows the same patterns as the official ASP.NET Core AuthenticationHandlers but is simpler for getting started. If you're not concerned about the configurability and reusability aspects, the implementation I've provided should be fit for purpose.

Asp.net Core 2 API POST Objects are NULL?

I have a .net Core 2 API setup with some test function. (Visual Studio 2017)
Using postman I do a post with the raw data to that method, but the model is just blank? Why?
// POST api/Product/test
[HttpPost]
[Route("test")]
public object test(MyTestModel model)
{
try
{
var a = model.SomeTestParam;
return Ok("Yey");
}
catch (Exception ex)
{
return BadRequest(new { message = ex.Message });
}
}
public class MyTestModel
{
public int SomeTestParam { get; set; }
}
You need to include the [FromBody] attribute on the model:
[FromBody] MyTestModel model
See Andrew Lock's post for more information:
In order to bind the JSON correctly in ASP.NET Core, you must modify your action to include the attribute [FromBody] on the parameter. This tells the framework to use the content-type header of the request to decide which of the configured IInputFormatters to use for model binding.
As noted by #anserk in the comments, this also requires the Content-Type header to be set to application/json.
To add more information to the accepted answer:
There are three sources from which parameters are bound automatically without the use of an Attribute:
Form values: These are form values that go in the HTTP request using
the POST method. (including jQuery POST requests).
Route values: The set of route values provided by Routing
Query strings: The query string part of the URI.
Note that Body is NOT one of them (though I think it should be).
So if you have values that need to be bound from the body, you MUST use the attribute binding attribute.
This tripped me up yesterday as I assumed that parameters from the Body would be bound automatically.
The second minor point is that only one parameter can be bound to the Body.
There can be at most one parameter per action decorated with [FromBody]. The ASP.NET Core MVC run-time delegates the responsibility of reading the request stream to the formatter. Once the request stream is read for a parameter, it's generally not possible to read the request stream again for binding other [FromBody] parameters.
Thus if there is more than one parameter you need, you need to create a Model class to bind them:
public class InputModel{
public string FirstName{get;set;}
public string LastName{get;set;}
}
[HttpPost]
public IActionResult test([FromBody]InputModel model)...
The Docs
Alternatively, add the [ApiController] attribute to your controller.
This has annoyingly affected me so many times (months apart) that I want this answer visible.
I deal with this issue for some hours. This problem stems from several reasons. Let's consider the request is Reactjs (javascript) and backend (API) is Asp .Net Core.
in the request, you must set in header Content-Type:
Axios({
method: 'post',
headers: { 'Content-Type': 'application/json'},
url: 'https://localhost:44346/Order/Order/GiveOrder',
data: order,
}).then(function (response) {
console.log(response);
});
and in the backend (Asp .net core API) u must have some setting:
1. in Startup --> ConfigureServices:
#region Allow-Orgin
services.AddCors(c =>
{
c.AddPolicy("AllowOrigin", options => options.AllowAnyOrigin());
});
#endregion
2. in Startup --> Configure before app.UseMvc() :
app.UseCors(builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
3. in the controller before action:
[EnableCors("AllowOrigin")]
In my case I had { get; set; } missing in my .cs model which results in an object with all members null on POST.

Blocking anonymous access by default in ASP .NET 5

My team and I are starting up a new website project in ASP .NET 5 and I'm trying to set up the basis of our user authentication and authorization policy.
So far, I've managed to use the [Authorize] and [AllowAnonymous] attributes to selectively define an authorization policy controllers or actions. The one thing I'm still struggling to achieve is defining a default authorization policy.
Bascially, I'd like every controller and action to behave as if they had an [Authorize] attribute by default, so that only actions specifically tagged as [AllowAnonymous] can be accessed by an anonymous user. Otherwise, we expect that, at some point, someone will forget to add an [Authorize] attribute to their controller and introduce vulnerabilities into the webapp.
It is my understanding that what I'm trying to do could be achieved in previous versions of ASP .NET by adding the following statement in FilterConfig.cs:
filters.Add(new AuthorizeAttribute());
... except that FilterConfig.cs no longer exists in MVC 6. According to How to register a global filter with mvc 6, asp.net 5 I can now access the global filters list using:
services.ConfigureMvc(options =>
{
options.Filters.Add(new YouGlobalActionFilter());
}
... tried it, looks fine, but now it's the AuthorizeAttribute filter that I can't seem to find.
For experimenting purposes I've tried to handcraft an equivalent to the AuthorizeAttribute filter and came up with the following:
public class LoginFilter: AuthorizeFilter
{
public LoginFilter(): base(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build())
{
}
public override Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.AuthorizationContext context)
{
if(!context.HttpContext.User.Identity.IsAuthenticated && context.ActionDescriptor is ControllerActionDescriptor)
{
var action = context.ActionDescriptor as ControllerActionDescriptor;
if(!AcceptAnonymous(action.ControllerTypeInfo) && !AcceptAnonymous(action.MethodInfo))
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
}
return base.OnAuthorizationAsync(context);
}
private static bool AcceptAnonymous(ICustomAttributeProvider o)
{
return o.IsDefined(typeof(AllowAnonymousAttribute), true);
}
}
This kinda works... I can add it to the global filters list, and it does reject queries coming from unauthenticated users unless the query is resolved to an action tagged [AllowsAnonymous].
However...
the AuthorizationPolicyBuilder thingy is ugly and misleading: it does not serve any purpose and is apparently ignored during the whole processing. The only reason I added it is that AuthorizeFilter requires an AuthorizationPolicy in its constructor. I guess, but haven't tried yet, that directly implementing IAsyncAuthorizationFilter would solve this particular issue
nothing in this code is specific to my webapp and the functionality was apparently provided in previous versions of the framework, so I'm willing to bet that there already is (or there will soon be) a component doing exactly the same thing, and I'd rather use a standard component from the framework than handcraft my own.
So, long story short, where has the AuthorizeAttribute filter gone? Or is there any functional equivalent I can use to make rejection of anonymous users the default behavior?
You can use Microsoft.AspNet.Mvc.AuthorizeFilter.
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Authorization;
services.ConfigureMvc(options =>
{
options.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()));
});
If you need custom authorization requirements see this answer for more information.

Categories

Resources