Search code examples
.netsharepoint-2010httpwebrequestwindows-authenticationwcf-rest

Consuming custom REST service hosted inside Sharepoint 2010 with Windows Authentication


There is this custom WCF REST service hosted inside a Sharepoint 2010 application using Windows Authentication.

I need to consume this service in a .NET project (i.e. console project) but the credential thing is driving me crazy. I'm getting lots of 400 - bad request or 401 - unauthorized responses (depending wether I include some authentication or not).

Let me give you an example:

siteUri is the url of the SP site (the root/home page) and methodUri is the url of the service method that returns an XML.

Again, this site is windows authenticated. If I open the browser and go to methodUri I get:

Request Error

The server encountered an error processing the request. See server logs for more details.

But if then I go to siteUri, the Sharepoint home page loads and then I can browse to methodUri again and now I get the XML response correctly.

So it seems that browsing to the homepage stores some cookies that then are used when making the request to the web method. Checking the requests in Fiddler I can confirm this:

The first methodUri request fails with 400 code and doesn't have any Cookies in the request header. Then when I browse the homepage (siteUri) there are a few requests/responses that seems to be the authentication itself. The response header includes:

WWW-Authenticate: Negotiate oRswGaADCgEAoxIEEAEAAABDh+CIwTbjqQAAAAA=
Set-Cookie: WSS_KeepSessionAuthenticated={b89d78b2-063d-4ac4-810e-bde4e04a829e}; path=/
Persistent-Auth: true

And last, when browsing to methodUri again first response is 401 - Unauthorized, but just after that there is another request including this headers

Cookie: WSS_KeepSessionAuthenticated={b89d78b2-063d-4ac4-810e-bde4e04a829e}
Authorization: Negotiate oXcwdaADCgEBoloEWE5UTE1TU1AAAwAAAAAAAABYAAAAAAAAAFgAAAAAAAAAWAAAAAAAAABYAAAAAAAAAFgAAAAAAAAAWAAAABXCiOIGAbEdAAAAD37/i76Dl3jNyK8bIRz57fmjEgQQAQAAAPUXp1AtIpqEAAAAAA==

that finally are accepted and the service responds with the corresponding XML.

So the browser is smart enough to handle this back and forth requests/responses to authenticate the current windows user.

My problem is, how can I do this in .NET (3.5 or 4) but WITHOUT using the defautl credentials, I need to specify the username, pwd and domain.

I've spent 5 days dealing with this and I could make it work using default network credentials in the HttpWebRequest, but when I set the credentials manually (using the same as the current logged in user) it doesn't work.

I just find this whole thing unnecesarily over-complicated (thank you Microsoft) because I come from an open source (ruby, python) background and this kind of things are just plain easy.

Hope I'm clear, if you need any extra information just ask. I'm willing to give my whole reputation in a bounty for this.

Thanks


Solution

  • Well, I've fixed it.

    This is the code I've ended up using. Thanks to Microsoft for putting things so easy and well-explained.

    var siteUri = @"http://sharepoint-site-uri";
    var methodUri = @"http://sharepoint-site-uri/namespace/service/method";
    string responseXml = null;
    
    var request = (HttpWebRequest)WebRequest.Create(siteUri);
    var cc = new CredentialCache { { new Uri(siteUri), "NTLM", new NetworkCredential("user", "pwd", "domain") } };
    request.Credentials = cc;
    
    try
    {
        // first site request
        var response = (HttpWebResponse)request.GetResponse();
        if (response.StatusCode == HttpStatusCode.OK)
        {
            request = (HttpWebRequest)WebRequest.Create(uri);
            request.CookieContainer = new CookieContainer();
    
            // copy the authentication cookies from the response
            foreach (Cookie c in response.Cookies)
            {
                request.CookieContainer.Add(c);
            }
    
            request.ContentType = "application/xml; charset=utf-8";
    
            // second request, now against the service url
            using (var reader = new StreamReader(request.GetResponse().GetResponseStream()))
            {
                // get the response text
                responseXml = reader.ReadToEnd();
            }
        }
    }
    catch (Exception e)
    {       
       // handle error
    }