ASP.NET MVC 5 - Dynamically set CORS - c#

In my MVC5 application, I've a controller that can be called from client applications (AngularJs in my case) and returns JSON.
For this particular Controller/Action, I want to set the Access-Control-Allow-Origin dynamically, depending of the "client_id" params in QueryString.
My problem is that I can't find a way to handle the Http Options request from the browser...
I've try many solutions:
1- [HttpOptions] Attribute on top of the Action Controller:
The code is never executed called
[HttpOptions]
public virtual ActionResult Refresh(string client_id)
{
// This code is never executed
// Should be setting CORS depending on the client_id
}
[HttpPost]
public virtual ActionResult Refresh(string client_id, FormCollection form)
{
// This is executed after the browser Http Options Request
}
2- Create my own ActionFilter, and put the CORS Headers from this filter: Never handle the Http Options Request.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class ApplicationCrossOriginAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// This code is never executed on Http Options Request
// Set custom CORS header here on Http Options Request
base.OnActionExecuting(filterContext);
}
}
3- Use Global.asax Application_BeginRequest Event: Once again, the code is never reach with an Http Options Request...
I've so tried to simply configure CORS from the Web.config file, but I don't want to hardcode the Origins (and I can't since it's all domains are dynamic...)
But, yeah, everything works perfectly with this configuration:
<system.webServer>
<httpProtocol>
<customHeaders>
<clear/>
<add name="Access-Control-Allow-Origin" value="*" /><!--Not dynamic here-->
<add name="Access-Control-Allow-Methods" value="GET,POST,OPTIONS" />
<add name="Access-Control-Allow-Headers" value="Accept,Authorization" />
</customHeaders>
</httpProtocol>
</system.webServer>
I'm stuck here, as i don't know how to fill my needs...

Related

Asp.Net Core 3.1 405 Method Not Allowed

Hi Guys I need help.. I always get 405 Method Not Allowed
I'm using Asp.Net Core Web Application 3.1, I dont have problem with HttpGet but when i use HttpPost it always return 405 Status Code
Here is the My Controller
[Route("api/[controller]")]
[ApiController]
public class ExamController : ControllerBase
{
[HttpPost("PostValue")]
public ActionResult<HttpResponseMessage> PostInfo([FromBody] PersonalInfo info)
{
string json = JsonConvert.SerializeObject(info);
HttpClient client = new HttpClient();
var response = client.PostAsync("https://sampleapi/receive", new StringContent(json, Encoding.UTF8, "application/json"));
if (response.IsFaulted)
return BadRequest(response);
return Ok(response);
}
}
This is my Startup Class
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddCors(c =>
{
c.AddPolicy("AllowOrigin", options => options.AllowAnyOrigin());
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStatusCodePages();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseCors(options => options.AllowAnyOrigin());
}
Here is the sample image of URL and the Result
Looking at the provided image, you use chrome to issue the url request, which is a HTTP GET command. So, your app got an HTTP GET command but your method wants to accept an HTTP POST method. That's why it says 'method not allowed'.
If you want to try http commands, you need a web test tool such as PostMan.
Configure the CORS middleware properly. Add .AllowAnyMethod() after options.AllowAnyOrigin() as a chain. You may end up to this for testing purposes: app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
As another option, make sure that your web server (likely IIS) allows POST HTTP method besides GET.
In addition to the postman test method, another way to test the post request is to use ajax to send post request in jquery, here is a sample:
<script>
$(function () {
$("#send").click(function () {
var personalInfo = { Id: $('#Id').val(), Name: $('#Name').val() };
$.ajax({
url: 'http://localhost:50855/api/Exam/PostValue',
type: 'POST',
contentType: "application/json; charset=utf-8",
dataType: 'json',
data: JSON.stringify(personalInfo),
//success: function (data, textStatus, xhr) {
// console.log(data);
//},
//error: function (xhr, textStatus, errorThrown) {
// console.log('Error in Operation');
//}
});
});
})
</script>
<form id="form1">
Id : <input type="text" name="Id" id="Id" />
Name: <input type="text" name="Name" id="Name" />
<input type="button" id="send" value="Send Post Data" />
</form>
Here is the test result:
I'm just going to add to this thread with my issue and resolution to same in case someone else stumbles upon this.
I was receiving a 405 error code, despite the action being decorated with the HttpPost and ValidateAntiForgeryToken attributes, and ensuring the posted form data included the anti-forgery token. Everything worked fine locally, but as soon as I put everything up on the server that's when I started receiving the 405 error. Turns out this error had nothing to do with what I had done in my app. It was actually an issue in a stored procedure in my MySQL database. Locally, case sensitivity isn't an issue, but on the server I had upper-cased the name of a table that was in lower-case, which caused an error to bubble up and give me this very obscure 405 error.
I guess lesson learned here is to check EVERYTHING :)
Edit: For clarity, my issue relates to MySQL and Linux Hosting. Not sure if same would apply if using Windows hosting solutions
My solution was literally as stupid as adding a "/" to the end of my request URL. Lost hours over it. GET worked fine without it.
I am working on .Net 5 Api project, and came across this same issue.
Adding lines below to the auto-generated web.config file when release is done:
<modules>
<remove name="WebDAVModule" />
</modules>
to the <system.webServer> part of web.config and
<handlers>
<remove name="WebDAV" />
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,PUT,DELETE,DEBUG" type="System.Web.Handlers.TransferRequestHandler" resourceType="Unspecified" requireAccess="Script" preCondition="integratedMode,runtimeVersionv4.0" responseBufferLimit="0" />
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
fixed it for me. Some of the lines are probably a bit of an overkill, but I have philosophy "if it may not work in future, better to have it overengineered".
However, I was struggling a lot with Azure DevOps CI-CD pipelines.
I managed to do it, and wrote about how I did it here:
Azure DevOps Release Pipeline Web.Config Edit
Okay so I've been reading about removing the WebDav by entering in the web.config but that didn't work for me for core 3.1. What you need to do is remove it from the IIS by:
Expand the server in IIS 8
Select the site
Click on handling mappings (For the site not server)
Search for WebDav and remove it.
Restart IIS
Web.config should be left alone. when i added the remove name=WebDav, my API stopped working.
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
note: you may need to also remove the one from the server if that doesn't work as I removed that first when I was troubleshooting.
Add [Route("MethodName")] in the header of the method in the controller. Just like:
[Route("api/[controller]")]
[ApiController]
public class AccountController : Controller
{
[Route("getusers")]
public async Task<User> GetUsers()
{
...
}
}

.Net Core 2.2 Web API 405

I am trying to setup a .net core 2.2 web api to use a post verb. Anything other than a get verb returns a 405 no matter if it is run on my local machine (w10 iis eXPRESS 10.0) or the windows server (2016 R2 IIS 8.0). I've read the other posts about disabling WebDav in your config file, adding a route, and completely removing the WebDav feature. I have done all of those to no avail. I'm just starting to develop in core and find this baffling, on the same server is a non-core web api that runs on .net framework 4.5 that processes GET,PUT,POST,DELETE without error. And yes, I have restarted the server after making changes to any of the configurations. The following are the web.config changes that I made, the last one coming directly from MS. Basic project that reproduces the same error on my machine and server is here https://github.com/FranciscanMedia/error405_core/tree/master it is just a standard web api project you get when you fire up VS2019.
<system.webServer>
<handlers accessPolicy="Read, Script">
<remove name="WebDAV" />
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit"
path="*."
verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS"
modules="IsapiModule"
scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll"
preCondition="classicMode,runtimeVersionv4.0,bitness64"
responseBufferLimit="0" />
</handlers>
</system.webServer>
<system.webServer>
<modules>
<remove name="WebDAVModule" />
</modules>
<handlers>
<remove name="WebDAV" />
</handlers>
</system.webServer>
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules runAllManagedModulesForAllRequests="false">
<remove name="WebDAVModule"/>
</modules>
</system.webServer>
<system.webServer>
<handlers accessPolicy="Read, Script">
<remove name="WebDAV" />
<remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
<add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit"
path="*."
verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS"
modules="IsapiModule"
scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll"
preCondition="classicMode,runtimeVersionv4.0,bitness64"
responseBufferLimit="0" />
</handlers>
</system.webServer>
Short answer
It could be as simple as that. The reason is routing.
Just send your POST request to right URL like https://localhost:44327/api/values/123.
Detailed explanation
It's not the issue. It works as expected.
You make a GET request to https://localhost:44327/api/values/. It returns 200 OK.
But when you make a POST request to the same URL https://localhost:44327/api/values/. It says 405 Method not allowed.
However, you get 405. It is happening because you are hitting the GET endpoint with POST method.
Microsoft Docs says:
... the HTTP client sent a valid JSON request to the URL for a Web API application on a web server, but the server returned an HTTP 405 error message which indicates that the PUT method was not allowed for the URL. In contrast, if the request URI did not match a route for the Web API application, the server would return an HTTP 404 Not Found error.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/testing-and-debugging/troubleshooting-http-405-errors-after-publishing-web-api-applications
If you simply remove the GET endpoint. The POST request will start returning 404 Not found. Which means that you are not hitting any registered route.
To send POST request you need to use different URL according to the routing rules.
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// POST api/values
[HttpPost("{val}")]
public StatusCodeResult Post()
{
return Ok();
}
}
This attribute-based configuration means that route of your POST endpoint is /api/Values/{val}. Where {val} is any value. It's not processed in the endpoint.
If you want to process it, you should pass it to the method:
[HttpPost("{val}")]
public StatusCodeResult Post(string val)
{
return Ok();
}
I think that in your controller you have to import another library.
Try
using System.Web.Http;
Instead of
using Microsoft.AspNetCore.Mvc
Looking at what you have defined:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
Then for the action:
[HttpPost("{val}")]
public StatusCodeResult Post()
{
return Ok();
}
Your routing matches the following url:
https://localhost:44327/api/values/StatusCodeResult
It is going to take your main route defined on your controller [Route("api/[controller]")]
Then you are defining the "template" to use "{val}"
This is telling it to use the ActionResult specific name and to expect var val to be passed/appened.
Checking out the official documentation here: https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-2.2
under section "Token replacement in route templates ([controller], [action], [area])"
They specifiy:
For convenience, attribute routes support token replacement by enclosing a token in square-braces ([, ]). The tokens [action], [area], and [controller] are replaced with the values of the action name, area name, and controller name from the action where the route is defined. In the following example, the actions match URL paths as described in the comments:
[Route("[controller]/[action]")]
public class ProductsController : Controller
{
[HttpGet] // Matches '/Products/List'
public IActionResult List() {
// ...
}
[HttpGet("{id}")] // Matches '/Products/Edit/{id}'
public IActionResult Edit(int id) {
// ...
}
}
If you want it to just route based on just verbs (follow a pattern where each api endpoint just handles operations for that specific object) then you would change your post method to just
[HttpPost]
public ActionResult Post(string val)
{
return Ok();
}
I totally agree with #Vladimir's answer. I dont have enough points to add comments to the answer by #vlaimir so i am adding my thoughts and suggestions.
The code you have on your github,
// POST api/values
[HttpPost("{val}")]
public StatusCodeResult Post()
{
return Ok();
}
This is a post and it would expect a value {val} per the route action configuration. Since you may try to hit the post without any value, thats not permitted. Ensure you supply some value and then do the POST. If you are using POSTMAN, you may have to supply the BODY of your request with some value. Swagger is a great util tool to embed into the web api's and that comes with excellent intuitive UI for our routes/resources. That might be even ideal to help determine and ensure you supply the right value.
Otherwise, you dont need to modify or worry about your IIS or IIS Express settings. or webdav.

"The requested resource does not support http method 'OPTIONS'" when using EnableCors

I want to enable CORS on one specific action in an Asp.net Web Api. Here's how I'm trying to do it:
[Route("api/mycontroller/myaction")]
[HttpPost]
[EnableCors("https://example.com", "*", "post")]
public async Task<IHttpActionResult> MyAction()
{
...
}
But when I send an OPTIONS request to the route, I get back an error: "The requested resource does not support http method 'OPTIONS'." I also tried removing the [HttpPost] annotation to no avail.
What am I missing?
For me, I added the following headers to the request by adding the following code to the Application_BeginRequest function of the Global.asax.cs file:
protected void Application_BeginRequest()
{
if (Request.Headers.AllKeys.Contains("Origin", StringComparer.CurrentCultureIgnoreCase)
&& Request.HttpMethod == "OPTIONS")
{
Response.AddHeader("Access-Control-Allow-Headers", "content-type", "accept", "pragma", "cache-control", "authorization");
Response.End();
}
}
I have little idea why this works.
Out of curiosity, I tried adding all headers by using an asterisk but then Web API complained that the Authorization header was missing.
You've probably missed the higher level call to HttpConfiguration.EnableCors, as described here: https://enable-cors.org/server_aspnet.html.
Add this code to your configuration:
public static void Register(HttpConfiguration config)
{
// New code
config.EnableCors();
}
To ensure the OPTIONS request gets handled by your application code and not some other part of the system before it reaches your app code, you may try adding the following to your web.config:
<system.webServer>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<remove name="TRACEVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
You might also need to include:
<add name="OPTIONSVerbHandler" path="*" verb="OPTIONS"
modules="IsapiModule" requireAccess="None"
scriptProcessor="C:\Windows\System32\inetsrv\asp.dll"
resourceType="Unspecified" />
See the answer at IIS hijacks CORS Preflight OPTIONS request.
Or maybe even just this:
<add name="OPTIONSVerbHandler" path="*" verb="OPTIONS"
modules="ProtocolSupportModule" requireAccess="None" />
If none of that on its own works, then in your global.asax or other code you might try:
if (filterContext.HttpContext.Request.HttpMethod == "OPTIONS")
{
filterContext.HttpContext.Response.Flush();
}
…or some other variation on that, for example:
if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OridinalIgnoreCase)
&& Request.HttpMethod == "OPTIONS") {
Response.Flush();
}
Regardless of what specific code you use to do it, the point is to:
make sure OPTIONS requests are actually getting caught/handled by your application code—not caught/handled by some other part of the system before ever reaching your app code
make sure you have explicit handling for OPTIONS requests in your application code
make the OPTIONS handling in your application code just do Response.Flush()
Or another approach I’m not sure is relevant to your situation as coded but I’ll mention just in case:
public HttpResponseMessage Options()
{
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK
};
return response;
}

.NET Core MVC set redirect for role authorization failure

I've been looking for a way to control where the user is redirected to if an authorization fails. I've seen a lot of Stackoverflow topics on this regarding CookieAuthentication and the LoginPath attribute, but I'm not using cookie authentication (see the implementation below).
Is there a way to redirect to a non-authorized page and not just get a 403 error in the browser?
Current authorization implementation
In web.config
<configuration>
<system.webServer>
...
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="true" />
</system.webServer>
</configuration>
In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthorization(
options =>
options.AddPolicy("FooUsersAndAdmins",
policy => policy.RequireRole(#"FOODOMAIN\users-group", #"FOODOMAIN\admins-gropu")));
}
Use across different controllers
[Authorize(Policy = "FooUsersAndAdmins")]
public class HomeController : Controller { ...

ASP.NET MVC: Programmatically set HTTP headers on static content

I have an ASP.NET application with a filter wired up in RegisterGlobalFilters that performs the following:
public class XFrameOptionsAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(System.Web.Mvc.ResultExecutingContext filterContext)
{
filterContext.HttpContext.Response.AddHeader("X-FRAME-OPTIONS", "SAMEORIGIN");
}
}
Looking in Fiddler, I can see that views returned from the webserver include this header. Static files however, such as JavaScript do not include this header in the HTTP response.
How do I get ASP.NET MVC to also apply this filter to any static files the web server returns?
One way to set headers for all the content of site is in web.config. The customHeaders section will make sure that this header is included for all files and responses.
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="X-FRAME-OPTIONS" value="SAMEORIGIN" />
</customHeaders>
</httpProtocol>
</system.webServer>
Another option is to create custom HttpModule as shown below. This way you have more control on the files and content to which headers needs to be appended.
namespace MvcApplication1.Modules
{
public class CustomOriginHeader : IHttpModule
{
public void Init(HttpApplication context)
{
context.PreSendRequestHeaders += OnPreSendRequestHeaders;
}
public void Dispose() { }
void OnPreSendRequestHeaders(object sender, EventArgs e)
{
// For example - To add header only for JS files
if (HttpContext.Current.Request.Url.ToString().Contains(".js"))
{
HttpContext.Current.Response.Headers.Add("X-FRAME-OPTIONS", "SAMEORIGIN");
}
}
}
}
And then register them in web.config as shown below -
<system.webServer>
<modules>
<add name="CustomHeaderModule" type="MvcApplication1.Modules.CustomOriginHeader" />
</modules>
</system.webServer>
This is something that if you want on every request (static or dynamic requests), you should probably set it up through IIS (the web server). Here are some details on different ways that you can achieve this - http://www.iis.net/configreference/system.webserver/httpprotocol/customheaders
In short, you could do this in your web.config file
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="X-Custom-Name" value="MyCustomValue" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
If you have access directly to IIS, you can use the UI to set this up as well.

Categories