Hybrid and Combined SharePoint provider hosted addin

SharePoint 2013 provider hosted addins template in Visual studio comes with one disadvantage  - It is designed for either onprem or online. Also, it is designed for a single app.

My customer's requirements were simple-

1. Hybrid: There should be only 1 web project in visual studio for SharePoint online and on-premises. So, the app/add-in should consider both S2S and ACS authentication at the same time. In other words it should support low-trust as well as high-trust authentication within the single web application.

2. Combined: There should be a single IIS web application and single visual studio project for 3 SharePoint Provider hosted add-ins. All the 3 add-ins should redirect to the same web app with different start pages.

Following is the scheme that was required for ClientId/ClientSecret/IssuerId for the apps

Name

On-Prem

Online

Addin1

ClientId1/IssuerId1

ClientId1/ClientSecret1

Addin2

ClientId2/IssuerId1

ClientId2/ClientSecret2

Addin3

ClientId3/IssuerId1

ClientId3/ClientSecret3

Both the above requirements were solved by tampering TokenHelper.cs and SharePointContext.cs classes that come as a part of Visual studio template. Following post specifies generic guidelines and changes that have to be done to meet above requirements.

1. Client Id, Client Secret, ClientSigningCertificatePath, ClientSigningCertificatePassword and IssuerId have to be present in the web.config of our app. One important point to note here is the following code

public static bool IsHighTrustApp() { return SigningCredentials != null; }

This code in tokenhelper decides if the S2SContextProvider to invoke or ACSContextProvider to invode. Please note that SingingCredentials is just a property that checks if there is any certificate path provided in web.config and prepares that certificate. We changed it to true/false depending upon the request URL. If it was coming from On-Prem SP farm then return true else return false. Also, note that this property is static. So, if you don't change the code above, it will get instantiated before any code is executed and it will always assume that the app is hightrust app. So, beware.

2. In the request pipeline, the App invokes series of methods to load SharePoint context, save SharePoint context, get client id/client secret etc. We inserted our own methods in each of SharePoint's methods. This way, we could be minimally invasive and still achieve the desired results. Following is the hierarchy of the context.

diag1

So, we need to change following methods in both the overridden classes.

1. LoadSharePointContext

2. SaveSharePointContext

Apart from the methods, changes have to be done in

ClientId and ClientSecret static properties.

Now for our requirement 2, Client id and Client secret were different for different app. So, we modified the static properties to below and wrote our logic to fetch client id and client secret depending upon the URL of the app.

private static string ClientId = ContextHelper.GetClientId();
private static string ClientSecret = ContextHelper.GetClientSecret();

GetClientId was implemented to something like below

public static string GetClientId() { try { string currentUrl = HttpContext.Current.Request.RawUrl; ////Get the title of the current AddIn using the current url AddinSection section = (AddinSection)ConfigurationManager.GetSection("Addins"); AddIn addin = section.Addins.Cast<AddIn>().First(a => currentUrl.ToLower().Contains(a.Url.ToLower())); ////Return the ClientId corresponding to the current AddIn return addin.ClientId; } catch (Exception ex) { return string.Empty; } }

The above logic is applicable to all the following methods. In all the below methods, the original call is replaced by our helper call. This makes sure, every time following methods are called, we are passing appropriate context and details as per the incoming URL.

LoadSharePointContext

LoadSharePointAcsContext

LoadSharePointHighTrustContext

SaveSharePointContext

Few things about Load and Save SharePoint context. The tokenhelper uses a session variable to save and load context. So, we created 3 more session variables to load and then save context.

Also, there is a cookie that gets created in SaveSharePointContext, which is called spCacheKeyCookie. This needs to be changed to create 3 more cookie variables depending on our url that comes through.

Last but not the least, we added RefreshClientId method in our SharePointContextFilterAttribute's OnExecuting method. This method just takes the client id and client secret again from the config depending upon the incoming URL. This is the first method that gets called, when any add-ins controller is invoked. This ensures that we are always taking correct client id even if the user is switching between our apps.

And that's about it. With all the above changes we were able to create a single Visual studio web project that serves for 3 different apps in 2 different environment!

Thanks to my teammate Sonia Suhag.

Complete code sample below:

Hosting Multiple Addins