Welcome to my blog, stay tunned :
Home | Blogs | Stephane Eyskens's blog

SharePoint 2013 - Consuming custom WCF services from SharePoint-Hosted Apps in Authenticated Mode

Hi,


I don't know if you ever tried the following :

  • Create and deploy a custom WCF service inside of SharePoint using one of the built-in factories
  • Consume that WCF service from a SharePoint-Hosted App, ie via JavaScript

but if you did, you probably encountered the following problem:


The endpoint /_vti_bin/yourendpoint is not accessible in the context of a SharePoint App


You had that either by trying to consume the service right from the browser using the App url as host header or by trying to use the SharePoint cross domain libraries. This problem is due to the fact that SharePoint only allows out of the box REST & CSOM endpoints consumption from Apps so if you deploy custom WCF services, it won't be allowed from within the App.


If you use sp.webrequestinfo cross-domain library, you'll notice that it will work providing your service is consumable anonymously but what if you have your own service that requires authentication?

Remember that in the context of a SharePoint-Hosted App, your only ways to consume web services are:

  • with JavaScript
  • using an External Content Type but that only works for data services and I'm not sure that it works if your service requires authentication

So, given the above, one way to achieve our goal is to use CORS but it will require some extra efforts before it works. If you don't know about CORS, I encourage you to read a previous blog post of mine http://www.silver-it.com/node/152 but you can keep reading this post, it's not absolutely required to know bits & bites about CORS to go ahead.

Basically, CORS enables web browsers to workaround the same-origin policy. In the context of a SharePoint-Hosted App, the pages will run under a domain like this:


http(s)://apps-.......whatever.com/


so, whatever JavaScript call performed under that domain will be considered cross-domain. If you want to consume your own web service, none of the built-in cross-domain libraries will help because they are either restricted to OOTB calls (targeting _api or CSOM calls) or anonymous intranet/internet calls via SP.WebRequestInfo.

However, using CORS, you can achieve both goals which are being able to perform the cross-domain call and being able to forward the credentials of the current user to your web service. In a nutshell, your service should return the following response headers when called:

  • Access-Control-Allow-Origin
    Access-Control-Allow-Methods
    Access-Control-Allow-Credentials

The problem you will have is that, the service in this case is hosted inside of SharePoint using for instance the MultipleBaseAddressWebServiceHostFactory and therefore, it is not so easy to return the expected response headers. In a regular WCF hosted outside of SharePoint, you could easily add a routine in global.asax or implement a custom behavior that would return them. In SharePoint, it's better not to touch the global.asax and trying to add extra endpoint behaviors result in some WCF issues because you interfeer with SharePoint's mechanism to resolve custom services. There might be a way to add custom end point behaviors to SharePoint Hosted WCF services but after half a day of headaches, I finally gave up.

So, one way to return the expected headers is to do it via a custom HTTP module (or via a reverse-proxy, IIS...see my series "Integration Challenges" for the options).

However, you'll have to implement a specific logic to return the CORS headers. Indeed, when setting Access-Control-Allow-Credentials to true, the Access-Control-Allow-Origin header cannot be a wildcard *. It must return a specific origin (or more than 1) or the request will be rejected by the browser. As you know, for the same SharePoint app, you will have one origin per app instance...and therefore, you can't afford to specify a static value to the Access-Control-Allow-Origin header, otherwise it will only work for one instance of a given app.


So, now that the theory is explained, here is the code:


Create a web service (I don't detail the steps here since there is nothing new) that has one method returning the current user:

return SPContext.Current.Web.CurrentUser.LoginName;

just to make sure that your service is well able to return the consuming identity.


Create an HTTP Module that does this:

void IHttpModule.Init(HttpApplication context)
{
    context.PreSendRequestHeaders += (sender, e) =>
    {
        var response = context.Response;               

        if(!string.IsNullOrEmpty(context.Request.Headers["Origin"]) &&
            context.Request.Headers["Origin"].IndexOf("apps") !=-1)
        {
            response.AddHeader("Access-Control-Allow-Origin", context.Request.Headers["Origin"]);
            response.AddHeader("Access-Control-Allow-Methods", "*");
            response.AddHeader("Access-Control-Allow-Credentials", "true");
            if (context.Request.HttpMethod.ToUpperInvariant() == "OPTIONS")
            {

                response.StatusCode = (int)HttpStatusCode.OK;
            }
        }                
    };

}

Basically, I check whether the request contains a Origin header. If it does, I check whether the Origin comes from our app domain. You could put a regular expression that checks the app prefix + the app domain. Then, I specify that the incoming origin is allowed and I set the other required response headers. I'm also handling the OPTIONS verb for CORS preflight requests.

So, in a nutshell, I'm allowing all my apps to consume any kind of URL via CORS. If I want to be more restrictive, I can also check the target URL and even implement a black list or origins or black list of services. For sake of simplicity, I'm hard-coding things here but you could clearly have another logic as for instance reading a FARM property bag (or web app) that would contain white/black lists etc...
Anyway, when you have deployed your module, you can start consuming your service in JavaScript from your SharePoint-Hosted App the following way:

$.ajax({
   crossDomain: true,
    xhrFields: {
        'withCredentials': true
    },
    url: "http://win-kmvsccl3iqd/_vti_bin/yourdirectory/yourrestservice.svc/segment1/segment2...",                   
    success: function (d) {
        console.log("ok");
    },
    failure: function (err) {
        console.log(err);
    }
});

Note here the usage of widthCredentials:true that will cause the browser to forward authentication information to our custom WCF service. Since this guy is hosted inside of SharePoint, it relies on the same authentication mechanisms. If you wanted to do the same with a WCF hosted outside of SharePoint, you should then make this WCF claims-aware + trust the SharePoint STS via a common certificate.
Of course, all of the above will only work with a modern browser...


Happy Coding!

Comments

WCF claims-aware +Trust the SharePoint STS

Hi Stephane,

This is really a great article, thank you so much for it. I am mostly concerned in how to make the last option where a WCF service is hosted outside SharePoint, to be honest I cannot find any hints on this except your mention, and I've been trying this for the past few days with no major outcome.

I've installed VS WCF claims aware templates, when I execute with WCF Test Client, it works and I can see the claim id, yet when I consume from a SharePoint-hosted app I end up with errors.

On the other hand, I could not also find much documentation on establishing WCF/SharePoint STS trust, so I assumed that what applies to a Provider-hosted app applies to WCF too, and accordingly updated service's config with ClientSigningCertificatePath, ClientSigningCertificatePassword, etc ...

I hope that you can help me out with the above scenario as there is no clear clues how to do this.

Thanks a lot.
M

WCF outside of SharePoint

Hi,

Go read my previous comment about the Claims token held by SharePoint:

http://www.silver-it.com/node/159#comment-9601

Now, other possibilities are:

- Use ACS (Azure Control Services) as a trusted broker for both SharePoint and your remotely hosted service
- Use Azure Active Directory applications and authenticate your request with either ADALJS, or server-side with ADAL.

If you can afford it, just host your WCF/WebApi in Azure, you can perfectly talk to it from the inside from both a client and a server (providing outbound trafic is allowed on 443).

Best Regards

How to allow any domain

Hello,

I am tryning to get only the module working in a SharePoint 2010 environment. But I want to allow an ajax call coming from any domain (mobile app)
Can you tell me how to modify the code for this?

Much appreciated.
Kind regards,
Mario@delafini.com

Any domain

Hi,

Any domain is incompatible with the Access-Control-Allow-Credentials http header. When using that guy, you must specify a specific domain. If you don't need this one, hen you can simply return a Access-Control-Allow-Origin with a value of "*".

If you need both, you should find another way to authenticate.

But what you can do and it's basically what I do in my code, you can simply allow the domain that you receive in the incoming Origin HTTP request header. That way, you'll return a specific domain but it's dynamic and not defined statically.

Best Regards

Cross-domain/extrenal REST service

Hi,

Thank you for sharing this.
I cannot figure out how to call an external REST service using CORS and your AJAX method from a SharePoint-hosted app (Sharepoint online). In fact, no authorization header/credentials will be sent to external service using this code (or similar, using Angular services) because cookies will not be sent so I cannot authenticate calls from my service.

Any ideas ? Is it really possible to forward the SP STS security token to an external service ? Other clues ?

Thank you,
Alex.

Credentials

Hi,

It will work with withCredentials provided you return an exact origin from the server in the allowed-origin header. Only this combination works but I explain this in one of the reated post. Of course, you also need to make sure you use at least IE10 or Chrome (safari on ipad works too, didn't try the others) since the browser plays a key role in that story.

Another element : in my explanation, my external service is deployed against SharePoint via _vti_bin/....but is considered external by the app model. However, it shares the same authentication scheme as the SharePoint web app. In your case, your service is truly an external service outside of SharePoint and I doubt it is sharing the same authentication scheme.

So my guess is that the browser sends the credentials (check with Fiddler) but your service doesn't know what to do with it. Moreover, don't forget that the Claims Token held by SharePoint isn't a bootstrap token....Usually, in SPO, we work with the Secure Store to communicate with the external world but the secrure store isn't something you'll leverage in JS.

Best Regards

CORS in another SharePoint Farm

Thanks alot it worked within my SharePoint Farm as i was calling my WCF service from my SharePoint hosted App.. I was struggling with it for a couple of days. Now I have another challenge. My same WCF service is also being hosted in another SharePoint. I tried the same approach(CORS) to acess the WCF service from the SP App but it is not working. DO you have an idea?

HttpModule

Wanted to confirm that the http module you are creating is the new class library and then added that module and then referencing from sharepoint hosted app?

HTTP Module

Hi,

The HTTP Module is part of a Farm Solution package and must be deployed independently of your SharePoint Hosted App. Its purpose is to grant Apps from your AppDomain to leverage CORS functionalities by returning the appropriate HTTP response headers to the client.

With the HTTP Module, you can decide to enable one or more Apps From the App itself, you don't have to do any kind of reference to the module. Simply, you'll have to make sure you enable cross-domain (if you use jquery for instance) and make sure that the "origin" HTTP header is part of your request. The HTTP module will trap the incoming request and return the appropriate headers.

Note that there are other ways to setup that kind of things. You can configure IIS (but that must be done on each WFE) to return CORS headers or configure some rules at the reverse proxy level if applicable in your situation.

Best Regards

CORS with another Farm

Hi,

Whether it's another farm or not doesn't change anything since the browser doesn't care about the target domain. However, you need to make sure that the credentials you're using are allowed on the remote farm.

When using CORS+forward credentials, the browser will simply forward the current credentials to the server, in this case, a WFE of the other farm. If the target WebApp on the other farm is using the same authentication method (claims, classic?) and if this set of credentials is known and authorized, everything should be OK.

Best Regards

accessing custom WCF service from sharepoint hosted

Hi,

I am accessing custom WCF DATA service hosted on different server in Sharepoint hosted app using Jquery. I am currently using Sp.Webproxy to get the data. AS service is anonymoualy accessible i am able to. But client wants to have authentication on the service. So problem arises. I have to implement CORS. The article above explains how to consume server hosted in Sharepoint server. But client do not allow farm solutions to be deployed on the server. So i have to host my service on a separate server. Can you please let me know the extra steps i need to implement to make my sharepoint app work with custom wcf data service hosted outside sharepoint. This is kind of very urgent for me. Please assist.

Thanks
Ankur Garg

Accessing non-SharePoint hosted web services

Hi,

Indeed, in this article I'm only explaining how to access custom services hosted in SharePoint. If your service is hosted outside of SharePoint, it's going to be harder. For this to work, you must make your web service trust the same issuer as SharePoint.

I've done something similar in the past but not with JavaScript and as far as I remember, I had to use the SharePoint object model (server-side accessible only by full trust solutions, meaning farm solutions) to rebuild the boostrap token. In the authentication cookie, SharePoint doesn't contain all the necessary information so I doubt you could just afford to forward it to your web service using CORS.

I guess that in this case, a certificate could help but it's a totally different way of working.

Best Regards

Hi, Its working. I have not

Hi,

Its working. I have not done anything extra. Wcf data service has windows authentication enabled. Its working in sharepoint hosted app. Why? Please explain. I hope it will work on production as well. Please suggest if i need to take care of some extra steps.

Thanks