SharePoint-hosted Apps can't be safely licensed.

Jun 17
SharePoint-hosted Apps can't be safely licensed.

We all know that Microsoft has put a lot of effort in the new App model for SharePoint and Office. There's a great marketplace where ISV's and developers can publish applications for clients to try and then hopefully purchase.

store.png 

We currently have two (three) ways of hosting these applications.

 

Right now the most popular option in the marketplace is a SharePoint-hosted App. This makes sense from a complexity standpoint. You don't have to host a service and potentially manage multi tenancy since all assets are hosted in SharePoint. The "problem" with a SharePoint-hosted App is that there's no way to SAFELY enforce licensing... You might object to this since there's support for calling the licensing service from a SharePoint-hosted Application by having your Javascript call the licensing service via the server side WebProxy.

Sure you can do this but since all your code is client side Javascript we can easily hack this logic and fool the licensing logic. To make it even worse for App developers, we don't even have to reverse engineer the script since most applications follow the same pattern for checking the licensing..

How could MS create such a App story when they have spend so much time and effort on the marketplace? They must have known that the most popular hosting scenario is "completely" useless when it comes to licensing and for any serious business trying to make money on SharePoint-hosted Apps.

Below is a sample script that you can add to the applications master page. This script will "spoof" the response from the licensing service and thus trick the applications licensing logic to belive that the app is licensed.  Since Javascript is such a dynamic language we can intercept any method we want and modify the parameters. The script below is very simple, we rewrite the get_body method and there we just do a simple string replace on the returned string setting the license type to "Paid". ​Once again the beauty of this approach is that you don't have to reverse engineer any obfuscated Javascript, just rewrite the message.

Add the script just before the closing </body> tag. There are some alerts added so you can follow the "flow" when running.

Remember that many Apps cache the result from the license check so you might have to clear your cookies after adding the scipt to the master page.

<script type="text/javascript">
(function(){
   var serviceUrl = 'https://verificationservice.officeapps.live.com/ova/verificationagent.svc';
   var originalNotify = NotifyScriptLoadedAndExecuteWaitingJobs;
   
   NotifyScriptLoadedAndExecuteWaitingJobs = function(s){
      if( s == "sp.js" ){
         //alert("sp.js loaded");
	 hookIt();
      }
      var ret = originalNotify.apply(this,arguments);
      return ret;
   }  
   function waitHook(){
      if( SP && SP.SOD ){
         SP.SOD.executeFunc('sp.js', 'SP.WebProxy', hookIt);
      }else{
         window.setTimeout( waitHook, 10 );
      }
   }
   waitHook();

   function hookIt(){
      function intercept( fp ){
         return function(context, request){
	    var response;
		
	    var executeQueryAsync = context.executeQueryAsync;
	    context.executeQueryAsync = function( success, failure ){
	       alert( "executeQueryAsync" );

  	       executeQueryAsync.call(context, mySuccess, myFailure );

   	       function mySuccess(sender,args){
	          alert("mySuccess :: here we can manipulate the arguments");
	          success.apply(this,arguments);
	       }
	       function myFailure(sender,args){
	          alert("myFailure :: here we can manipulate the arguments");
	          failure.apply(this,arguments);
	       }
	    }

  	    response = fp.apply(this,arguments);
	    var realGetBody = response.get_body;
	    response.get_body = function(){
	       alert("get_body called " + request.get_url() );
	       var s = realGetBody.apply(response,arguments);
	       if( request.get_url().indexOf(serviceUrl) != -1 ){
	          alert("get_body >> " + s);
	          s = s.replace('<IsTest>true</IsTest>','<IsTest>false</IsTest>');
	          s = s.replace('<IsValid>false</IsValid>','<IsValid>true</IsValid>');
	          s = s.replace('<EntitlementType>Trial</EntitlementType>','<EntitlementType>Paid</EntitlementType>');
	          alert("The updated get_body updated xml >> " + s);
	       }
	       return s;
            }
            return response;
         }
      }
      SP.WebProxy.invoke = intercept( SP.WebProxy.invoke );
   }

})();
</script>
 
So how do we overcome this problem? There's only one "safe" way and that is to do the licensing check server side! This means that you will have to manage a service somewhere. This also means you will have a "provider-hosted application" or a "hybrid" approach.
 
I strongly belive that this "hybrid" approach is the best solution and it will soon be the de facto standard if you want to sell your App with a trial period. We can host some assets in SharePoint (but why bother?) and service the rest from our service. Think about it as a CDN. You gain a lot from this architecture. Easier patching and upgrading of the application, no need to submit the code to MS for review. You can provide a trial period without having to worry about someone hacking your licensing logic since now you are verifying the license server side. And even though you might not provide a licensed application this "hybrid" approach should be something to consider. Microsoft is telling us to not install managed code on the server but I'm asking why should we install ANYTHING except a "trampoline"?
 
So if you are creating Apps for SharePoint ​that are not free remember that SharePoint-hosted applications can never be "safely" licensed.
 

I really don't understand why Microsoft didn't add the licensing check in SharePoint??? I do understand that they wanted to provide the app author freedom to build whatever UX they wanted but that doesn't mean you should have to check the licensing in your Javascript. Do the check in SharePoint and have SharePoint servicing different assets based on the licensing result. 

 
Here's a screencast showing the script in action.
 
  
 
More to come....
 
/Jonas