Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enable both CORS support and NTLM authentication

I have a self-hosted c# consol web API. It services several web applications that use AngularJS to perform async http requests. It needs to have both CORS support and NTLM Authentication.

I currently have both enabled, but it would appear that the way that I have them implemented causes them to cancel themselves out. I believe this is cased by my initial 401 response from the server. This usually results in an authentication handshake between the two but, looking at it in Fiddler, this initial 401 response lacks a Access-Control-Allow-Origin tag, and this causes the browser to abandon the handshake. I have taken an application and given them the same host name to disable the need for CORS, and the handshake works perfectly. I have disabled NTLM authentication by replacing my custom NtlmSelfHostConfiguration with the original HttpSelfHostConfiguration, and the Access-Control-Allow-Origin tag executes perfectly to allow CORS. it is only when they are both active that things don't work.

UPDATE: I placed a Breakpoint in my CorsHandle on a request that would not require it, just to make sure that the webserver can actually call it while using the NtlmSelfHostConfiguration, and it was successfully hit. I just wanted to make sure that there was nothing in the NtlmSelfHostConfiguration that physically prevented the Corshandle from ever being called. It all works fine, It definitely appears that I need to find a way to call CorsHandle even on requests that fail authentication.

Here is my implimentation:

program.cs:

using System.Web.Http;
using System.Web.Http.Validation.Providers;
using System.Web.Http.SelfHost;
namespace DCMAPI
{
    class Program
    {
        static void Main(string[] args)
        {
            string BaseAddress = "http://localhost:8080/";

            //NtlmSelfHostConfiguration is defined in HttpSelfHostConfiguration.cs  
            //it inherits from HttpSelfHostConfiguration 
            //and enables Ntlm Authentication.

            var config = new NtlmSelfHostConfiguration(BaseAddress);

            config.Routes.MapHttpRoute(
                "API Default",
                "api/{controller}/{id}", 
                new { id = RouteParameter.Optional });

            //CorsHandler is also defined in CorsHandler.cs.  It is what enables CORS

            config.MessageHandlers.Add(new CorsHandler());

            var appXmlType =
            config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault
                (t => t.MediaType == "application/xml");
            config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);                   

            config.Services.RemoveAll
                (typeof(System.Web.Http.Validation.ModelValidatorProvider),
                v => v is InvalidModelValidatorProvider);

            using (HttpSelfHostServer server = new HttpSelfHostServer(config))
            {
                server.OpenAsync().Wait();
                Console.WriteLine("running");
                Console.ReadLine();
            }

        }
    }
}

NtlmSelfHostConfiguration.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.SelfHost;
using System.Web.Http.SelfHost.Channels;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace DCMAPI
{
    public class NtlmSelfHostConfiguration : HttpSelfHostConfiguration
    {
        public NtlmSelfHostConfiguration(string baseAddress)
            : base(baseAddress)
        { }

        public NtlmSelfHostConfiguration(Uri baseAddress)
            : base(baseAddress)
        { }

        protected override BindingParameterCollection OnConfigureBinding
            (HttpBinding httpBinding)
        {
            httpBinding.Security.Mode =
                HttpBindingSecurityMode.TransportCredentialOnly;

            httpBinding.Security.Transport.ClientCredentialType =
                HttpClientCredentialType.Ntlm;

            return base.OnConfigureBinding(httpBinding);
        }
    }
}

CorsHandle.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Net.Http;
using System.Threading.Tasks;
using System.Threading;
using System.Net;

namespace DCMAPI
{
    public class CorsHandler : DelegatingHandler
    {
        const string Origin = "Origin";
        const string AccessControlRequestMethod = "Access-Control-Request-Method";
        const string AccessControlRequestHeaders = "Access-Control-Request-Headers";
        const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
        const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
        const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";

        protected override Task<HttpResponseMessage> SendAsync
            (HttpRequestMessage request, CancellationToken cancellationToken)
        {

            bool isCorsRequest = request.Headers.Contains(Origin);
            bool isPreflightRequest = request.Method == HttpMethod.Options;
            if (isCorsRequest)
            {

                if (isPreflightRequest)
                {
                    HttpResponseMessage response = 
                        new HttpResponseMessage(HttpStatusCode.OK);

                    response.Headers.Add(AccessControlAllowOrigin,
                        request.Headers.GetValues(Origin).First());

                    string accessControlRequestMethod =
                        request.Headers.GetValues(AccessControlRequestMethod)
                        .FirstOrDefault();
                    if (accessControlRequestMethod != null)
                    {
                        response.Headers.Add(
                            AccessControlAllowMethods, accessControlRequestMethod);
                    }

                    string requestedHeaders = string.Join(", ",
                        request.Headers.GetValues(AccessControlRequestHeaders));
                    if (!string.IsNullOrEmpty(requestedHeaders))
                    {
                        response.Headers.Add(AccessControlAllowHeaders,
                            requestedHeaders);
                    }

                    TaskCompletionSource<HttpResponseMessage> tcs = 
                        new TaskCompletionSource<HttpResponseMessage>();
                    tcs.SetResult(response);
                    return tcs.Task;
                }
                else
                {
                    return base.SendAsync(request,
                        cancellationToken).ContinueWith<HttpResponseMessage>(t =>
                    {
                        HttpResponseMessage resp = t.Result;
                        resp.Headers.Add(
                            AccessControlAllowOrigin,
                            request.Headers.GetValues(Origin).First());
                        return resp;
                    });
                }
            }
            else
            {
                return base.SendAsync(request, cancellationToken);
            }
        }
    }
}
like image 494
JHixson Avatar asked Apr 28 '13 23:04

JHixson


1 Answers

This might be little bit late and I'm not sure that it will help you, but it might help someone else looking to have both NTLM and CORS enabled.

I'm using Web API 2 for the backend REST API. And AngularJS for the frontend.

First off, since you're using NTLM, your calling XMLHttpRequest must send credentials. In AngularJs you do like this:

$http.get(url, { withCredentials: true });

Then, your must enable CORS on your backend (in WebApiConfig.Register()):

        var enableCorsAttribute = new EnableCorsAttribute("*", "*", "*")
            {
                SupportsCredentials = true
            };
        config.EnableCors(enableCorsAttribute);

SupportsCredentials = true means that we will add Access-Control-Allow-Credentials to the response.

The wildcards mean that we allow:

  • CORS from any origin
  • All request headers
  • Any HTTP Verb (method)
like image 100
Joel Avatar answered Sep 30 '22 02:09

Joel