I have a Web API 2 project from VS 2013, using the 5.0.0.0 DLLs from Nuget (very recently dloaded).
I also created a custom CorsPolicy that limits origins. This seems to work fine, and I followed the directions here: http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api
What I've noticed with Fiddler is that, while the OPTIONS verb is properly blocked with a 400 Bad Request, the POST verb is passed straight through to the controller, and then the CorsPolicy is invoked, but by that time, the Post action has succeeded, and the client gets a 200 OK return.
I am expecting the POST to be blocked with a 400 Bad Request as well as the OPTION verb. If I understand the CORS TR correctly, it should be blocked.
Here's the diagnostic tracing from Web API from a single POST, where I have used Fiddler to set the Origin header to http://localhose. Bear in mind -- the exact same scenario correctly gets blocked with the OPTION verb.
w3wp.exe Information: 0 : Request, Method=POST, Url=http://myhost/myvdir/api/v1/MyCntrllr/MyAction, Message='http://myhost/myvdir/api/v1/MyCntrllr/MyAction'
w3wp.exe Information: 0 : Message='MyCntrllr', Operation=DefaultHttpControllerSelector.SelectController
w3wp.exe Information: 0 : Message='MyNamespace.Controllers.MyCntrllrController', Operation=DefaultHttpControllerActivator.Create
w3wp.exe Information: 0 : Message='MyNamespace.Controllers.MyCntrllrController', Operation=HttpControllerDescriptor.CreateController
w3wp.exe Information: 0 : Message='Selected action 'MyAction(Submission submission)'', Operation=ApiControllerActionSelector.SelectAction
w3wp.exe Information: 0 : Message='Value read='{ MyValue1: 24000, MyValue2: 2, MyValues3: 24,34, MyValue4: 0, MyValue5: 90001, MyValue6: c0, MyValue7: 16 }'', Operation=JsonMediaTypeFormatter.ReadFromStreamAsync
w3wp.exe Information: 0 : Message='Parameter 'submission' bound to the value '{ MyValue1: 24000, MyValue2: 2, MyValues3: 24,34, MyValue4: 0, MyValue5: 90001, MyValue6: c0, MyValue7: 16 }'', Operation=FormatterParameterBinding.ExecuteBindingAsync
w3wp.exe Information: 0 : Message='Model state is valid. Values: submission={ MyValue1: 24000, MyValue2: 2, MyValues3: 24,34, MyValue4: 0, MyValue5: 90001, MyValue6: c0, MyValue7: 16 }', Operation=HttpActionBinding.ExecuteBindingAsync
w3wp.exe Information: 0 : Message='Action returned 'MyNamespace.MyConclusion'', Operation=ReflectedHttpActionDescriptor.ExecuteAsync
w3wp.exe Information: 0 : Message='Will use same 'JsonMediaTypeFormatter' formatter', Operation=JsonMediaTypeFormatter.GetPerRequestFormatterInstance
w3wp.exe Information: 0 : Message='Selected formatter='JsonMediaTypeFormatter', content-type='application/json; charset=utf-8'', Operation=DefaultContentNegotiator.Negotiate
w3wp.exe Information: 0 : Operation=ApiControllerActionInvoker.InvokeActionAsync, Status=200 (OK)
w3wp.exe Information: 0 : Operation=MyCntrllrController.ExecuteAsync, Status=200 (OK)
w3wp.exe Information: 0 : Message='CorsPolicyProvider selected: 'MyNamespace.WhiteListOriginPolicyProvider'', Operation=CorsPolicyProviderFactory.GetCorsPolicyProvider
w3wp.exe Information: 0 : Message='CorsPolicy selected: 'AllowAnyHeader: True, AllowAnyMethod: False, AllowAnyOrigin: False, PreflightMaxAge: null, SupportsCredentials: False, Origins: {https://www.example.com,http://localhost:22221}, Methods: {POST,OPTIONS}, Headers: {}, ExposedHeaders: {}'', Operation=WhiteListOriginPolicyProvider.GetCorsPolicyAsync
w3wp.exe Information: 0 : Message='CorsResult returned: 'IsValid: False, AllowCredentials: False, PreflightMaxAge: null, AllowOrigin: , AllowExposedHeaders: {}, AllowHeaders: {}, AllowMethods: {}, ErrorMessages: {The origin 'http://localhose' is not allowed.}'', Operation=CorsEngine.EvaluatePolicy
w3wp.exe Information: 0 : Operation=CorsMessageHandler.SendAsync, Status=200 (OK)
w3wp.exe Information: 0 : Response, Status=200 (OK), Method=POST, Url=http://myhost/myvdir/api/v1/MyCntrllr/MyAction, Message='Content-type='application/json; charset=utf-8', content-length=unknown'
w3wp.exe Information: 0 : Operation=JsonMediaTypeFormatter.WriteToStreamAsync
w3wp.exe Information: 0 : Operation=MyCntrllrController.Dispose
Code:
WhiteListOriginPolicy
public class WhiteListOriginPolicy
: CorsPolicy
{
public WhiteListOriginPolicy()
{
AllowAnyHeader = true;
AllowAnyMethod = false;
Methods.Add(HttpMethod.Post.ToString());
Methods.Add(HttpMethod.Options.ToString());
foreach (var origin in Settings.Default.WhiteListOrigins)
{
Origins.Add(origin);
}
}
}
WhiteListOrigins is a StringCollection from the web.config file
WhiteListOriginPolicyProvider
public class WhiteListOriginPolicyProvider
: ICorsPolicyProvider
{
public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult((CorsPolicy) new WhiteListOriginPolicy());
}
}
CorsPolicyProviderFactory
public class CorsPolicyProviderFactory
: ICorsPolicyProviderFactory
{
private readonly ICorsPolicyProvider _whiteListOriginsPolicyProvider = new WhiteListOriginPolicyProvider();
public ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
{
return _whiteListOriginsPolicyProvider;
}
}
WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.EnableSystemDiagnosticsTracing();
config.SetCorsPolicyProviderFactory(new CorsPolicyProviderFactory());
config.EnableCors();
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}",
defaults: new { action = RouteParameter.Optional }
);
}
}
WhiteListOriginPolicyAttribute
I think this is redundant, but it doesn't matter whether I use this or not
public class WhiteListOriginPolicyAttribute
: Attribute
, ICorsPolicyProvider
{
public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult((CorsPolicy) new WhiteListOriginPolicy());
}
}
MyCntrllrController
[RoutePrefix("api/v1/MyCntrllr")]
public class MyCntrllrController
: ApiController
{
[HttpPost]
[HttpOptions]
[WhiteListOriginPolicy]
[Route("MyAction")]
public IMyConclusion MyAction([FromBody] Submission submission)
{
if (Request.Method == HttpMethod.Options)
{
return null;
}
if (null == submission)
{
var response = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent("Request content body does not contain recognizable Submission data")
};
throw new HttpResponseException(response);
}
var engine = new CalcEngine(new DataLocatorService());
var eligibility = engine.GetEligibility(submission);
return eligibility;
}
}
The OPTIONS verb in action.
Request
OPTIONS http://myhost/myvdir/api/v1/MyCntrller/MyAction HTTP/1.1
Accept: */*
Origin: http://localhose
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, accept
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Host: myhost
Content-Length: 0
DNT: 1
Connection: Keep-Alive
Pragma: no-cache
Response
HTTP/1.1 400 Bad Request
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Wed, 20 Nov 2013 20:32:03 GMT
Content-Length: 59
{"Message":"The origin 'http://localhose' is not allowed."}
The POST verb in action.
Request
POST http://myhost/myvdir/api/v1/MyCntrller/MyAction HTTP/1.1
Accept: */*
Origin: http://localhose
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, accept
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Host: myhost
Content-Length: 121
DNT: 1
Connection: Keep-Alive
Pragma: no-cache
Content-Type: application/json
{"myvalue1":24000,"myvalue2":"2","myvalues3":["24","34"],"myvalue4":0,"myvalue5":"90001","myvalue6":"c0","myvalue7":"16"}
Response
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Wed, 20 Nov 2013 20:32:33 GMT
Content-Length: 460
_omitted_