Access-Control-Allow-Origin: * allows any website to access your resources. Always specify exact origins in production.
For WCF service you have to develop a custom behavior and include it in the endpoint configuration:
public class CustomHeaderMessageInspector : IDispatchMessageInspector
{
private readonly Dictionary<string, string> _requiredHeaders;
private readonly string[] _allowedOrigins;
public CustomHeaderMessageInspector(
Dictionary<string, string> headers,
string[] allowedOrigins)
{
_requiredHeaders = headers ?? new Dictionary<string, string>();
_allowedOrigins = allowedOrigins ?? new string[0];
}
public object AfterReceiveRequest(
ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel,
System.ServiceModel.InstanceContext instanceContext)
{
return null;
}
public void BeforeSendReply(
ref System.ServiceModel.Channels.Message reply,
object correlationState)
{
var httpHeader = reply.Properties["httpResponse"]
as HttpResponseMessageProperty;
if (httpHeader == null)
{
httpHeader = new HttpResponseMessageProperty();
reply.Properties["httpResponse"] = httpHeader;
}
// Get the request to check origin
var request = OperationContext.Current.RequestContext.RequestMessage;
var requestProps = request.Properties["httpRequest"]
as HttpRequestMessageProperty;
var origin = requestProps?.Headers["Origin"];
// Validate and set origin
if (!string.IsNullOrEmpty(origin) && _allowedOrigins.Contains(origin))
{
httpHeader.Headers.Add("Access-Control-Allow-Origin", origin);
httpHeader.Headers.Add("Vary", "Origin");
}
// Add other CORS headers
foreach (var item in _requiredHeaders)
{
if (!httpHeader.Headers.AllKeys.Contains(item.Key))
{
httpHeader.Headers.Add(item.Key, item.Value);
}
}
}
}
public class EnableCrossOriginResourceSharingBehavior :
BehaviorExtensionElement, IEndpointBehavior
{
public void AddBindingParameters(
ServiceEndpoint endpoint,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
// No implementation needed
}
public void ApplyClientBehavior(
ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
// No implementation needed
}
public void ApplyDispatchBehavior(
ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
// Define allowed origins (NOT wildcard)
var allowedOrigins = new[] {
"https://example.com",
"https://app.example.com"
};
var requiredHeaders = new Dictionary<string, string>();
// CRITICAL FIX: Use Access-Control-Allow-Methods (not Request-Method)
// Access-Control-Request-Method is a REQUEST header, not a RESPONSE header
requiredHeaders.Add(
"Access-Control-Allow-Methods",
"POST,GET,PUT,DELETE,OPTIONS"
);
requiredHeaders.Add(
"Access-Control-Allow-Headers",
"X-Requested-With,Content-Type,Authorization"
);
// Optional: Allow credentials
// requiredHeaders.Add(
// "Access-Control-Allow-Credentials",
// "true"
// );
// Preflight cache duration
requiredHeaders.Add(
"Access-Control-Max-Age",
"86400"
);
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(
new CustomHeaderMessageInspector(requiredHeaders, allowedOrigins)
);
}
public void Validate(ServiceEndpoint endpoint)
{
// No validation needed
}
public override Type BehaviorType
{
get { return typeof(EnableCrossOriginResourceSharingBehavior); }
}
protected override object CreateBehavior()
{
return new EnableCrossOriginResourceSharingBehavior();
}
}
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="crossOriginResourceSharingBehavior"
type="YourNamespace.EnableCrossOriginResourceSharingBehavior, YourAssembly, Version=1.0.0.0, Culture=neutral" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="corsEnabledBehavior">
<webHttp />
<crossOriginResourceSharingBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<service name="YourNamespace.YourService">
<endpoint address="api"
binding="webHttpBinding"
behaviorConfiguration="corsEnabledBehavior"
contract="YourNamespace.IYourServiceContract" />
</service>
</services>
</system.serviceModel>
[ServiceContract]
public interface IYourServiceContract
{
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "data")]
string GetData();
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "data")]
string PostData(DataContract data);
// Handle preflight OPTIONS requests
[OperationContract]
[WebInvoke(Method = "OPTIONS", UriTemplate = "*")]
void HandlePreflight();
}
public class YourService : IYourServiceContract
{
public string GetData()
{
return "Data";
}
public string PostData(DataContract data)
{
return "Success";
}
public void HandlePreflight()
{
// CORS headers are added by the message inspector
// Just return empty response with 204
WebOperationContext.Current.OutgoingResponse.StatusCode =
System.Net.HttpStatusCode.NoContent;
}
}
For new applications, consider migrating to ASP.NET Core Web API with much simpler CORS configuration:
// ASP.NET Core Web API - Much simpler!
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins("https://example.com")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
var app = builder.Build();
app.UseCors();
Critical Bug Fixed: The original code used Access-Control-Request-Method which is a request header sent by the browser, not a response header. The correct response header is Access-Control-Allow-Methods. This bug would cause CORS to fail.
Note: For more information on migrating from WCF, see the official migration guide and the community-supported CoreWCF project.
The content on this site stays fresh thanks to help from users like you! If you have suggestions or would like to contribute, fork us on GitHub.
Save 39% on CORS in Action with promotional code hossainco at manning.com/hossain