Skip to content

3

While some business applications are quickly moved to the cloud, others remain on-premises for the time being. To support user adoption, some items from the new world should be introduced to the old world. In this specific case, the term new world refers to Office 365, while the old world could be any other internal system not yet ready to move to the cloud.

So what is one of the most recognizible parts of Office 365? Of course the App Launcher, your quick-start to all Office 365 applications:

Office 365 App Launcher
The Office 365 App Launcher is available across all Office 365 services, like SharePoint, OneDrive, Word, Excel, Planner, Power BI, ...

Wouldn't it be nice if the legacy applications would have the same app launcher integrated into their UI? Of course it would be! But nice doesn't mean it's easy to do.

By trying to reverse engineer the Office 365 pages, there are some APIs used to gather the related information for the apps displayed in the app launcher. The most obvious request calls the following endpoint: https://portal.office.com/api/myapps/GetAllApps

Request to API GetAllAPps
Request to https://portal.office.com/api/myapps/GetAllApps returns all apps from the App Launcher

As seen in the above image, all relevant app information is returned from this API. Great, so why not directly use this API in your old world on-premises application? Unfortunately it's not that easy, at least. Trying to make the call client-side in the browser reveals the first major problem: Cross-Origin Resource Sharing (CORS) restrictions. Of course Microsoft won't allow any foreign domain to directly consume their resources, security comes first in the cloud. But there seems also to be no way to add an allowed origin for cross-domain requests for these types of API. Beside of this, the user must already be logged in to make a direct call to this API, which is not guaranteed if the user only logged in to his old corporate web site.

So why not trying to authenticate the user against Azure AD, getting his token and making a server-side request against this API with some Authorization headers? Sounds nice, too. But unfortunately, I wasn't able to succeed this way. This API obviously isn't meant to be consumed by other applications.

If we can't make it easy, let's make it the creative way!

By further investigating the App Launcher on Office 365, one will stumble upon the browser's local storage for the SharePoint domain (e.g. https://<tenant>.sharepoint.com/). In there, there is a key called SPSuiteLinksJson, and its value looks highly promising! It actually holds a large JSON string with user specific information. Two properties of the JSON object are exactly what we are looking for: NavBarData.WorkloadLinks and NavBarData.AppLinks. While the first holds all app information from the Apps overview of the app launcher, the second stores the additional apps visible in the App Launcher by clicking on the All Apps link. Bingo!

With that in mind, we can over-think the approach from calling an API and try to overcome authorization and CORS issues in another way.

The verified approach

Combining some good old techniques used in SharePoint years ago with some more fancy scripting styles, this leads to the following verified process: Include a SharePoint Online page, which has access to the local storage for its domain, inside the on-premises web page, and push the value from the SharePoint Online local storage to the parent frame!

Creating a proxy page in SharePoint Online

As always when it comes to SharePoint, there are some tricky steps to do to successfully make it working. By default, every SharePoint page includes an HTTP-Header x-frame-options: SAMEORIGIN in its response. This header ensures that a SharePoint page can only be i-framed in its own domain. Luckily, SharePoint offers a server-tag to avoid this response header:

<WebPartPages:AllowFraming runat="server" />

By adding this tag to a page (by editing its source-file, e.g. by editing the .aspx-page via explorer or SharePoint designer), it then can be i-framed from anywhere in the web.

Now that the page is allowed to be included inside another domain, let it push the required values from local storage to it's parent frame. Once the page is loaded, the parent frame is informed via postMessage (JavaScript) about the values from the SharePoint Online domain's local storage:

var suiteLinks = window.localStorage["SPSuiteLinksJson"];
window.top.postMessage(suiteLinks, "*");

Including the app data from SharePoint Online on the on-premises application

Now it is possible to embed the SharePoint Online proxy page in an (hidden) iFrame in the on-premises application. The only thing left to do for the on-premises page is to listen for the message event (JavaScript) to use the information from the SharePoint Online local storage:

window.addEventListener("message", function (e) {
  var suiteLinksStore = e.data;
  displaySuiteLinks(suiteLinksStore);
}, false);

The above variable suiteLinksStore now holds the local storage object from SharePoint Online. It can now be used to iterate over the WorkloadLinks and AppsLinks objects from the NavBarData object to get all the apps for the current signed in user with all required information like app title, URL, color and font-icon-class. With little effort, a replicated App Launcher in an on-premises environment can look like this:

Office 365 apps integrated in On-Premises App Launcher
Office 365 apps integrated in On-Premises App Launcher

Additional hints and considerations

  • Ensure that the proxy page in SharePoint Online is shared with everyone in your organisation
  • When adding the iFrame URL in your on-premises application to your proxy page, ensure to use (if possible) a Smart Link - something like¬†https://login.microsoftonline.com/login.srf?wa=wsignin1.0&whr=<your adfs server>&wreply=https://<your tenant name>.sharepoint.com/pages/your-apps-proxy-page.aspx instead of the direct link to your proxy page. This ensures the user is logged in via single sign on to the SHarePoint Online platform and the page can push the local storage to your application. Find more information about Smart Links here.
  • In your proxy page, ensure to check if the required key is already added to the local storage before pushing anything to the parent frame.
  • To not always load the iFrame with the SharePoint Online proxy page, consider adding the data consumed by your event listener to the on-premises domains local storage. Only include include the iFrame (call to SharePoint Online) if you don't have the corresponding information in your domains local storage or if the information is outdated.

 

Did you like my first blog post? Or need any further information about my approach of integrating the Office 365 App Launcher on-premises? Feel free to leave me a comment below.

 

This website stores some user agent data. These data are used to provide a more personalized experience and to track your whereabouts around our website in compliance with the European General Data Protection Regulation. If you decide to opt-out of any future tracking, a cookie will be set up in your browser to remember this choice for one year. I Agree, Deny
528