4
votes

I'm building a WCF Self-Hosted Data Service (OData, by the way) and I'm using Basic Authentication to authenticate users. It wasn't very hard, I just needed some configuration steps, build a UserNamePasswordValidator and a IAuthorizationPolicy - DONE.

Now I need to support CORS (Cross-Origin Resource Sharing). I've tried many implementations, some documented (for instance, this), other made by myself.

The problem is, if I let Basic Auth enabled, because the CORS preflight request (OPTIONS) doesn't have the 'Authorization' header, and I can't manipulate the request (of course, or that would defeat the purpose for the browser to do it), I'm unable to intercept/respond the request on the server. I can't even check how far does it go! I've tried to implement many Behaviours, Bindings, Managers, etc., but I can't catch that request, not even on "DataService<>.OnStartProcessingRequest()".

If I disable Basic Auth on the server side, I'm able to catch the CORS preflight request and eventually respond to it (using a IDispatchMessageInspector and a BehaviorExtensionElement), but that way I have to implement Basic Auth on my own... damn.

Please help me. How do I implement both? How can I intercept the CORS preflight request before Basic Auth simply respond 401 Unauthorized?

Thanks in advance.

2
Hi have you solved this? I'm having problems with this topic tooacostela
@acostela have you got the solution, i am facing the same issue.theburningfire

2 Answers

2
votes

First, you can handle all your "OPTIONS" request to allow them all. I use the following trick:
interface of my service:

/// <summary>Handles ALL the http options requests.</summary>
[WebInvoke(Method = "OPTIONS", UriTemplate = "*")]
bool HandleHttpOptionsRequest();

implementation :

/// <summary>Handles ALL the http options requests.</summary>
public bool HandleHttpOptionsRequest()
{
if (WebOperationContext.Current != null && WebOperationContext.Current.IncomingRequest.Method == "OPTIONS")
    {
        return true;
    }
    return false;
}

Second, you need to add Access-Control-Allow-Credentials in the "CORS enabler" enable-cors

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
    var requiredHeaders = new Dictionary<string, string>
        {
          { "Access-Control-Allow-Credentials", "true" }, 
          { "Access-Control-Allow-Origin", "*" }, 
          { "Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS" }, 
          { "Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Safe-Repository-Path,Safe-Repository-Token" }
        };

    endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new CustomHeaderMessageInspector(requiredHeaders));
}
0
votes

How I managed this problem in my case mentioned below:

 /// <summary>
/// Class written to check for whether the REST calls is authorised.
/// </summary>
public class RestServiceAuthorizationManger : ServiceAuthorizationManager
{
    /// <summary>  
    /// Method to check for basic authorization in rest service calls. 
    /// </summary>  
    protected override bool CheckAccessCore(OperationContext operationContext)
    {
        try
        {
            bool Verified = false;

            if (WebOperationContext.Current != null && 
                  WebOperationContext.Current.IncomingRequest.Method == "OPTIONS")
            {
                WebOperationContext.Current.OutgoingResponse.StatusCode = 
                HttpStatusCode.OK;
                return true;
            }
            else
            {
                //Extract the Authorization header, and parse out the credentials converting the Base64 string:  
                var authHeader = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];

                if ((authHeader != null) && (authHeader != string.Empty))
                {
                     //You code to check for authorization credentials from incoming authorization headers.
                }
                else
                {
                    //Throw an exception with the associated HTTP status code equivalent to HTTP status 401 
                    //No authorization header was provided, so challenge the client to provide before proceeding:  
                    //WebOperationContext.Current.OutgoingResponse.Headers.Add("WWW-Authenticate: Basic realm=\"RestServiceAuthorizationManger\"");
                    WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
                    WebOperationContext.Current.OutgoingResponse.StatusDescription = "Unauthorized";
                    return false;
                }
            }                
        }
        catch(Exception ex)
        {
            //Throw an exception with the associated HTTP status code equivalent to HTTP status 401 
            //No authorization header was provided, so challenge the client to provide before proceeding:  
            //WebOperationContext.Current.OutgoingResponse.Headers.Add("WWW-Authenticate: Basic realm=\"RestServiceAuthorizationManger\"");
            WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
            WebOperationContext.Current.OutgoingResponse.StatusDescription = "Unauthorized";
            return false;
        }
    }
}

public class EnableCorsSupportBehavior : IEndpointBehavior
{
    public void Validate(ServiceEndpoint endpoint)
    {

    }

    public void AddBindingParameters(ServiceEndpoint endpoint,
        BindingParameterCollection bindingParameters)
    {

    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
        EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(
            new CorsEnablingMessageInspector());
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint,
        ClientRuntime clientRuntime)
    {

    }
}

public class CorsEnablingMessageInspector : IDispatchMessageInspector
{
    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)
        };
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        var state = (dynamic)correlationState;            
                  
        // handle request preflight
        if (state != null && 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;
        }

        // add allowed origin info
        var response = (HttpResponseMessageProperty)reply.Properties[HttpResponseMessageProperty.Name];
        response.Headers.Add("Access-Control-Allow-Origin", "*");                
        response.Headers.Add("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,PATCH, OPTIONS");                
        response.Headers.Add("Access-Control-Allow-Headers", "Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization");               
        response.Headers.Add("Access-Control-Allow-Credentials", "true");
               
    }
}

Hope this may help someone. Thankyou!