ecLearn - Learning Management System built on top of Microsoft Dataverse for Power Platform and Dynamics 365 users

ENGINEERED CODE BLOG

Dynamics 365 Portal: Custom Server-side Code with a Companion App

I spent last week in Amsterdam attending both eXtreme365 and the User Group Summit, and was lucky enough to get to present three sessions on Dynamics 365 Portal topics. At each one of these sessions, one of the areas that generates the most interest is when I discuss the idea of a Companion App. Companion Apps are a technique you can use to leverage server-side code in your Portal implementation, and since it’s a topic I haven’t really covered on my blog, I thought it was about time.

What is a Companion App?

I believe it was Business Solutions MVP Colin Vermander who coined the term – or at the very least popularized it. In fact, he’s even got a GitHub project to act as a starter template (although I wish the original name of the project, Portal Buddy, had remained) [UPDATE: Portal Buddy lives on! Colin has moved his Companion App starter template to his own Git repository]. A Companion App is a web application (containing a web service) that you host yourself (either on-premise or in the cloud) that you interact with on the Portal using AJAX calls.

Because you host this app yourself, you have full control over it, including the ability to use server-side code. You can use server-side code to access APIs, including the Dynamics 365 API, or any other API you like. You control what functions your web service makes available to the JavaScript on your Portal.

However, introducing a Companion App to your Portal project is not without its complexities.

Hosting

As mentioned, you can host the Companion App pretty much anywhere you want, as long as it is accessible to the users of your Portal. That means technically if your Portal is only used by people within your firewall, your Companion App doesn’t need to be publicly accessible. Most of the time we find that organizations either host it using Azure Web Apps, or using an existing on-premise web server.

The Dynamics 365 Portal offering is Software-as-a-Service, so it means there are no servers to maintain. By introducing a Companion App, you’re adding infrastructure to a Portal project that does need to be maintained.

SSL Requirement

This is pretty straight-forward, but still worth mentioning. Since a Dynamics 365 Portal is hosted over HTTPS, any AJAX calls made from the Portal to another service also need to be over HTTPS. If you choose to host somewhere like Azure Web Apps, this shouldn’t be a problem as they include certificates for the *.azurewebsites.net domain, however I thought I’d mention it because if you’re developing locally, it’s important to configure HTTPS properly.

Cross-origin Resource Sharing

Another thing browsers don’t like is when you make an AJAX call to a domain that is different than the original page – this is called Cross-origin Resource Sharing (CORS). For example, if your JavaScript is coming from yourportal.microsoftcrmportals.com, but your AJAX call is to your Companion App hosted at yourcompanionapp.azurewebsites.net, the browser will reject that call unless you perform some additional configuration.

The configuration needs to happen on the server that is receiving the CORS request, so in this case the Companion App. If you’re hosting your app in Azure Web Apps, you can enable CORS directly in the Azure portal, without touching your code at all. Or, if you’re developing an ASP.NET you can add a few lines of code to enable CORS support. Keep in mind that when you enable CORS, you can specify which domains you accept requests from – although you can technically allow all domains, best practice is to limit this to only the domains you require (in this case, the domain of your Portal).

Caching

As with most things Portal-related, caching can cause headaches when implementing a Companion App. This is because any cache of the Dynamics 365 data your Companion App may have is different than the Portal cache.

The Portal relies on receiving cache invalidation messages via Azure Event Hub – these messages are triggered based on the Change Tracking feature in Dynamics 365. So it’s important that any relevant entities have Change Tracking enabled to ensure that the cache is updated when data is modified. However, this process isn’t instantaneous, so even when everything is configured properly, you may run into weird issues.

A good example is if you use a Companion App to create a record in Dynamics 365, and then immediately redirect the user to an Entity List that is rendered by the Portal. If the cache invalidation message hasn’t been received by the Portal yet, it’s possible that the record, even though it exists in Dynamics 365, may not appear in the list. A refresh a few seconds later may make the record appear, but that is not an ideal user experience.

I recommend avoid mixing out-of-the-box Portal features with Companion Apps for this reason. If you can segment your functionality in this way, you are less likely to run into odd caching behaviour.

Authentication

When you log into a Portal, your browser receives a cookie that is then passed to the Portal server on each subsequent request that tells the code who you are. Since cookies are domain-specific, the Companion App doesn’t get the cookie when you make a request to it. Even if it did, the Companion App wouldn’t be able to decrypt the information in the cookie in order to figure out which contact record is associated with the logged in user. So that means we need to figure out a way to share the authentication information with the Companion App, since in most cases it’s important for security to know who the logged in user is.

There are a few different options:

  • If you are using Azure AD B2C for authentication, check out the link above to Colin Vermander’s Companion App starter template. It already supports sharing the authentication of Azure AD B2C users between the Portal and the Companion App.
  • If you are using Azure AD, it is possible to modify Colin’s project to support that scenario as well, as Azure AD and Azure AD B2C are similar enough.
  • If you are using a different authentication provider, I’ve implemented an authentication sharing scheme using tokens and Liquid that could be of some help. I can share more with the community if there is interest.
  • If you can wait, then hopefully this problem will be solved for everyone shortly. This is because the April ’19 release notes have made mention of a new Portal feature that will allow us to obtain a token directly from the Portal that we can use to authenticate the user with our Companion App. This is similar to the technique Colin is using in his project, but will work regardless of the authentication provider you use.

Companion Apps are a great way to extend your Portal beyond the out-of-the-box capabilities. They certainly add complexity to a project, but when used properly they allow you to do almost anything while still leveraging the great capabilities of the Portal product.

19 responses to “Dynamics 365 Portal: Custom Server-side Code with a Companion App”

  1. […] post Dynamics 365 Portal: Custom Server-side Code with a Companion App appeared first on Engineered […]

  2. Richard says:

    Hi Nicholas,

    Is it possible to store user credentials in the Companion App, to allow global access to a certain application. If so, how can this be achieved?

    • Nicholas Hayduk says:

      Hi Richard,

      Yes, for sure. A Companion App is typically implemented as an ASP.NET website, so you’d store these credentials in the web.config file or an appsettings.json file. Since these are on the server only, these credentials are not viewable by users of the Portal.

      Nick

  3. Richard says:

    Hi Nick,

    Thanks for replying. I’m trying to implement the process you mention in “https://www.engineeredcode.com/blog/adding-sharepoint-integration-to-the-employee-self-service-portal-without-server-side-code-part-3” and use the liquid code as a way of automatic filtering the sharepoint data by the columns in the entity document location. For example by contact type, it will only show documents that contact should see. When I add the companion app to Azure app service I receive an “Unexpected Error” message.

    Richard

    • Nicholas Hayduk says:

      Hi Richard,

      Sorry, I’m not following. The process mentioned in that article is for when you don’t want to use a Companion App. If you are using a Companion App, then you can perform all the filtering within the Companion App, and you don’t have to worry about Liquid.

      Nick

  4. Richard says:

    Hi Nick,

    Sorry, Yes you are correct there was confusion on my part as I thought you had to reference the companion app….I now know I was wrong. So without using server side code, following your article “adding-sharepoint-integration-to-the-employee-self-service-portal-without-server-side-code-part-3” I was hoping the liquid would be able to filter the documents in sharepoint based on fields on the entity but it doesn’t seem to filter. Is this impossible? By liquid filter I mean in your code you filter by regarding object id, if I add another filter should it work.
    I hope that makes sense.

    • Nicholas Hayduk says:

      Can you give an example of what you mean by filtering documents in SharePoint based on fields on the entity?

      You might be able to do some filtering on the document locations (ie the SharePoint folders), because that is data stored in CDS. However, there is no reference to the individual documents in CDS, so you won’t be able to filter down at that level.

      So, as an example, if you had multiple document locations (folders) for an entity, you could use Liquid to only show a specific folder based on attributes on the entity.

      Nick

  5. Richard says:

    Hi Nick,

    So with liquid there is no way to access the sharepointdocument entity and filter by a custom field that matches a field in sharepoint?

    Richard

    • Nicholas Hayduk says:

      I don’t believe so. Liquid only has access to CDS entities, and individual SharePoint documents are not represented in the CDS data model.

      Nick

  6. Richard says:

    Okay. Thanks for your help Nick.
    Could you think of any way to achieve filtering of Sharepoint documents for contacts in the portal. Meaning contacts/customers who log into the portal see only specific documents related to them based on fields in the CRM. We originally did it at the entity level meaning we had a filtered entity list and than they can go to a specific record in that entity but we realised that as our customer document library grew we need to filter at metadata level. We also don’t want to create duplicate documents and send to a particular folder using Power Automate.

    Richard

    • Nicholas Hayduk says:

      You’d need to use a Companion App. Using the SharePoint API, you can perform the filtering you want.

      From the Portal, you call your Companion App – that Companion App knows the contact that is making the call, uses that to make the call to SharePoint. The Companion App then returns the results to your Portal for display.

      Nick

  7. Una says:

    Good morning Colin,

    Can Companion app pass say a client ID to a power BI embedded report to enable the report to be filtered to display that clients records only to a portal user?

    Thanks
    Una

    • Nicholas Hayduk says:

      Hi Una,

      To be honest, I don’t know enough about Power BI Embedded to say if a Companion App would be the best approach. With regular Power BI, you can use Liquid to include the filtering in the URL of the report to achieve this, but I’m not sure if the same technique applies to Power BI Embedded.

      Nick

  8. Brett says:

    Hi Nick,

    In article above you mentioned..

    “If you are using a different authentication provider, I’ve implemented an authentication sharing scheme using tokens and Liquid that could be of some help. I can share more with the community if there is interest.”

    I am interested, can you share? 🙂

    Thank you much in advance

    Brett

    • Nicholas Hayduk says:

      Hi Brett,

      The technique isn’t really relevant anymore now that we’ve got the OAuth Implicit Grant Flow, which allows you to get a token that contains the contact’s ID, and it works no matter with authentication provide you use.

      With that in mind, is it still of interest? If so, I could probably put together a post.

      Nick

  9. Una says:

    Good afternoon all,

    I have client using portal buddy and local auth in D365 portal, they are looking to move to B2C , they have number of react and angular apps running off portal via the buddy.

    whats involved in changing over the authentication to support Azure B2C, right now they note all handled in code and no authentication in place asume this would have to change ?

    • Nicholas Hayduk says:

      Instead of worry about how the users authenticate in your portal, use the OAuth 2.0 implicit grant flow to authenticate in your companion app: https://docs.microsoft.com/en-us/powerapps/maker/portals/oauth-implicit-grant-flow

      This way, even if you change auth providers, your code will still work.

      At a high level, this functionality allows you to request a token from the Portal itself. This token contains the ID of the contact who is logged in. You pass this token to the companion app, which is able to validate it using the public keys of the portal.

      Nick

  10. Guy says:

    Hi Nicholas, stumbled on this whilst trying to find out if anyone else had achieved what i was looking to implement. Maybe you have some knowledge on this:

    I was looking to use Power Automate to generate a Csv file and surface it to the Portal client browser as a response. This bit I’ve managed. The problem is honouring the Portal Security for dataverse records, for the given portal contact logged in.

    Passing the contact id would be straightforward, but honouring the query to only fetch the record that user is allowed to see would in my opinion require replicating what we have on the portal web roles etc.. server side within Power Automate and would have to be real time as to keep up with any changes.

    I assume this could be done by querying the dataverse tables for the contacts web roles etc.. but looks like it could be quite complex.

    Interested to know what your thoughts on this are. Maybe portal api can help, but didn’t see anything myself.

    Thanks for your time
    Guy

    • Nicholas Hayduk says:

      Hey Guy,

      You are correct, it would be complex. When implementing solutions via an external service like a Companion App, or in your case, Power Automate, we typically do not respect Web Roles, Entity Permissions, etc. Instead, we implement our own security model, which is typically based on the ID of the contact. However, it’s important that the ID not just be a parameter – it needs to be passed securely, otherwise anyone who knows the ID of another contact could use the service. Typically that is done using the implicit grant flow capabilities in portals, which you can use to get a token that can then be passed to the service. I’ve never tried to validate that token using Power Automate, so I’m not sure if that is possible.

      Nick

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact

Engineered Code is a web application development firm and Microsoft Partner specializing in web portals backed by Dynamics 365 & Power Platform. Led by a professional engineer, our team of technology experts are based in Regina, Saskatchewan, Canada.