Access to WCF WebService from JavaScript - Response to preflight request doesn't pass access control check

Viewed 1052

I am using a WCF (.svc) WebService and it's working perfectly - I can call its methods without problems from Postman, PHP, etc. But, when I try to call it from JavaScript/jQuery using AJAX, there is an obvious problem - I do it from other domain than WS, so it won't let me do it.

It is all about POST methods. But there is a problem even when my page is sending firstly an OPTIONS method:

OPTIONS 'WS ADDRESS' 405 (Method Not Allowed)

XMLHttpRequest cannot load 'WS ADDRESS' Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'MY ADDRESS' is therefore not allowed access. The response had HTTP status code 405.

There are detailed responses:

Responses

Okay, I read about cross-domain and WS's Web.config contains all of it that is necessary (?)

Web.config

Please let me know what am I doing wrong that I cannot reach my WS from JavaScript, even though it seems to me configured well. But still, it seems not to send these special headers in response... Thanks in advance.

2 Answers

You can implement .NET IDispatchMessageInspector for this work.

  • Create a class implementing IDispatchMessageInspector
  • Create a class implementing Attribute,IEndpointBehavior,IOperationBehavior

Allow only OPTIONS in your class implementing IDispatchMessageInspector

The code will look like this

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
using System.Collections.Generic;
using System.Net;

namespace WapstyPrintService
{
    public class MessageInspector : IDispatchMessageInspector
    {
        private ServiceEndpoint _serviceEndpoint;
        Dictionary<string, string> requiredHeaders;

        public MessageInspector(ServiceEndpoint serviceEndpoint)
        {
            _serviceEndpoint = serviceEndpoint;
            requiredHeaders = new Dictionary<string, string>();

            requiredHeaders.Add("Access-Control-Allow-Origin", "*");
            requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS");
            requiredHeaders.Add("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");
        }

        /// <summary>
        /// Called when an inbound message been received
        /// </summary>
        /// <param name="request">The request message.</param>
        /// <param name="channel">The incoming channel.</param>
        /// <param name="instanceContext">The current service instance.</param>
        /// <returns>
        /// The object used to correlate stateMsg. 
        /// This object is passed back in the method.
        /// </returns>
        public object AfterReceiveRequest(ref Message request,
                                              IClientChannel channel,
                                              InstanceContext instanceContext)
        {
            var httpRequest = (HttpRequestMessageProperty)request
            .Properties[HttpRequestMessageProperty.Name];
            return new
            {
                origin = httpRequest.Headers["Origin"],
                handlePreflight = httpRequest.Method.Equals("OPTIONS",
                StringComparison.InvariantCultureIgnoreCase)
            };
        }

        /// <summary>
        /// Called after the operation has returned but before the reply message
        /// is sent.
        /// </summary>
        /// <param name="reply">The reply message. This value is null if the 
        /// operation is one way.</param>
        /// <param name="correlationState">The correlation object returned from
        ///  the method.</param>
        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            var state = (dynamic)correlationState;
            if (state.handlePreflight)
            {
                reply = Message.CreateMessage(MessageVersion.None, "PreflightReturn");

                var httpResponse = new HttpResponseMessageProperty();
                reply.Properties.Add(HttpResponseMessageProperty.Name, httpResponse);

                httpResponse.SuppressEntityBody = true;
                httpResponse.StatusCode = HttpStatusCode.OK;
            }

            var httpHeader = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
            foreach (var item in requiredHeaders)
            {
                httpHeader.Headers.Add(item.Key, item.Value);
            }
        }
    }
}

and

using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace WapstyPrintService
{
    public class BehaviorAttribute : Attribute, IEndpointBehavior,
                                   IOperationBehavior
    {
        public void Validate(ServiceEndpoint endpoint) { }

        public void AddBindingParameters(ServiceEndpoint endpoint,
                                 BindingParameterCollection bindingParameters)
        { }

        /// <summary>
        /// This service modify or extend the service across an endpoint.
        /// </summary>
        /// <param name="endpoint">The endpoint that exposes the contract.</param>
        /// <param name="endpointDispatcher">The endpoint dispatcher to be
        /// modified or extended.</param>
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
                                          EndpointDispatcher endpointDispatcher)
        {
            // add inspector which detects cross origin requests
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(
                                                   new MessageInspector(endpoint));
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint,
                                        ClientRuntime clientRuntime)
        { }

        public void Validate(OperationDescription operationDescription) { }

        public void ApplyDispatchBehavior(OperationDescription operationDescription,
                                          DispatchOperation dispatchOperation)
        { }

        public void ApplyClientBehavior(OperationDescription operationDescription,
                                        ClientOperation clientOperation)
        { }

        public void AddBindingParameters(OperationDescription operationDescription,
                                  BindingParameterCollection bindingParameters)
        { }

    }
}

Then add your message inspector to service endpoint behavior

ServiceHost host = new ServiceHost(typeof(myService), _baseAddress);
foreach (ServiceEndpoint EP in host.Description.Endpoints)
            EP.Behaviors.Add(new BehaviorAttribute());
Related