Tuesday, 23 April 2019

SPFx isolated web parts – the right way to connect to your back-end API

It’s now possible for SharePoint/Office 365 developers to create *isolated* web parts, thanks to the recent release of SPFx 1.8. If your web part needs permission to talk to a back-end API or the Graph, you should strongly consider making your web part isolated. Simply having the Azure Function or other API secured with AAD authentication isn’t enough. In this post, I’ll talk through some of the details of an SPFx web part which can perform a privileged operation in your Office 365 environment (creating a Microsoft Team from a template in this case) – and therefore is something we should take steps to secure. Specifically, we want to protect the access token which is used behind the scenes, and ensure no other web parts can “piggy-back” onto the permissions we are using. Without isolated web parts:

  • Any other code on the page (perhaps from a 3rd party supplier) can sniff the access token, and potentially use it maliciously, depending on what permissions it has
  • Any standard SPFx web part can use the permissions from any other – meaning it’s a “highest common denominator” situation, where all standard SPFx web parts in the environment have the same permissions i.e. the highest that have ever been granted
These can be very valid security concerns – you’d want to be very sure about which permission levels have been granted to SPFx otherwise, and exactly what code you have in your environment. Isolated web parts can often be part of the answer in ensuring your Office 365 environment is not inadvertently insecure.

But how exactly do isolated web parts help?

Isolated web parts explainer

Web parts from an isolated SPFx package run on a unique domain, hosted within an iFrame - the permissions granted apply only to code hosted on that domain. Since regular SPFx web parts run on your *.sharepoint.com domain, the granted permissions do not apply there. And since any other SPFx isolated web parts run on a different unique domain, they don't apply there either. Essentially, the shared application principal for SPFx is not used – instead, an AAD app registration dedicated to this SPFx solution is created and used for authentication. Note this scope isn’t the individual web part, but the containing solution package. This is fine however - if you have an untrusted source contributing code to the very same solution/codebase, then you probably have bigger security and governance challenges to deal with quite frankly.

So when an isolated web part is added to your pages, you’ll see the containing iFrame – notice the source:

The source is a dynamically-created domain to represent this solution package – similar to how the old “SharePoint-hosted apps” model used to provide a separate domain for code-hosting for those who remember that. In the AD portal, you’ll also see the app registration that has been created on your behalf for this SPFx solution package:


It has a bunch of redirect URIs automatically configured, to support SPFx calling from the domain your iFramed web part code will use:

The combination of the unique domain/iFrame and dedicated AAD app registration gets around the previous trade-offs that came with SPFx and AAD integration, where a permission request for a given security scope (say, Group.ReadWrite.All – which would allow new Office 365 Groups or Microsoft Teams to be created or existing ones updated, as well as a bunch of other operations) would apply to *all* SPFx web parts and extensions in the tenant, not just the one that needed it.

So, you can see that any web part that performs a highly-privileged operation (e.g. creating a Team in my case, to continue that example) really should be isolated – especially if there’s a chance that your tenant could host web parts from different teams or providers. In this way. I am guaranteed that the only thing that can call my API is the web part(s) that are intended to - no other code in my Office 365 tenant will be able to.

Of course, without the iFrame that comes with isolated web parts, the auth token is right there in the page to be sniffed by anything else on the page:

So, isolated web parts are a good thing.

Before we talk about what configuration is needed where, let’s consider some user interface things for a second.

UI considerations with isolated web parts

Imagine you want to use some Office UI Fabric (OUIF) components within an isolated web part. That’s fine, but you need to consider the fact that your content is displayed within an iFrame which has certain dimensions – and certain UI components don’t play well with that. Let’s say we want to use a Dialog from OUIF React. In the example below, I have a button to pop the dialog, and for illustration I’ve also added a black border to my web part so you can see how big it is in the page:


When the button is pressed we get this:


Whoah – doesn’t look right. Of course, that’s because the Dialog is appearing within the iFrame only. If I extend the web part dimensions, the Dialog can be fully shown – effectively I almost need to “reserve” space on the page for any elements which are initially not visible:


And it’s the same with a Fabric React Panel. With a small web part, it’s not clear what’s going on at all:


But in this case, even using a taller web part doesn’t really help – you can see a bit more of the Panel, but since a Panel uses the full height of the screen you still won't see it all:


So, perhaps isolated web parts work better when the content is right there in the page, rather than with any elements that appear on user interaction. You’ll need to design the UI of isolated web parts accordingly.

Creating an isolated web part

Isolated web parts are created by specifying “isDomainIsolated” is true in the package-solution.json file in your SPFx project. This tells SPFx that all web parts in this package should be isolated from other web parts (but not from each other – you’d need separate projects for that):


You can specify this at any time, but the Yeoman Generator helps you out by asking this question when you’re creating a new SPFx project. A “yes” to the following question will result in the config above:


Key elements

I was slightly confused as what values I need in certain config values at first. Knowing that a new AAD app registration is created for me in AAD, is it THAT client ID that I need to use in my code? Or is it the client ID of the AAD app registration that my Azure Function uses internally to actually make calls against the Graph (since the pattern in my case is SPFx web part -> Azure Function -> Graph calls)?

The documentation essentially says that, “everything is done as normal”, but I was still a bit confused.

Here’s a summary of what you need to do:
  • Create your Azure Function app and secure with AAD authentication
  • Create your SPFx project and answer “yes” to the question about isolated web parts (or add “isDomainIsolated” : true in your existing package-solution.json file)
  • Add an appropriate “webApiPemissionRequests” section to your package-solution.json file (more on this next)
  • Write code in your SPFx web part using AadHttpClientFactory to perform the authentication against your API
  • Deploy the package to the app catalog (even if only debugging at this stage)
  • Approve the permission requests on the API management page in tenant administration
Let’s go into a bit more detail:
In package-solution.json
Add an entry into the “webApiPemissionRequests” section which corresponds to your server-side API/Function, with a scope of “user_impersonation”. IMPORTANT:
  • The AAD app reg/API to list is the one for your function app, NOT:
    • The dynamically-created AAD app for your isolated web part (e.g. “cob-teamcreatorwp” in my case)
    • Some other AAD app you have which you may be using inside your Function, but has no bearing on communication from the web part to the API (e.g. “COB Graph Access” in my case)
Your package-solution.json should therefore look something like this:

In your SPFx code
In your code, you’ll have something like this – notice that the Client ID to pass to AadHttpClientFactory is (again) the one for the Function app:

Approving the permissions:
The solution package then needs to be deployed to your tenant, and consent granted.
  • Add the package to the app catalog – notice the message that further action is needed on the Permissions Management Page, with a note about the specific permission to approve:

  • If you then head to that page, you’ll see the permission to approve:  

  • Hit the approve button if you’re satisfied with the permissions being granted. In this case, the permission will be scoped only to web parts within this SPFx package – other web parts in the tenant will not be able to use it.

Summary

There are lots of ways you can stay assured of what custom code can do in your Office 365/SharePoint Online environment, but isolated web parts can be really helpful in minimising the potential for exploits. Without them, maybe something else could call your back-end API without you realizing it. Notably, Microsoft still have some work to do in lowering the code permissions required for certain operations (e.g. posting a message in a Teams channel requires Group.ReadWrite.All, and certain Planner actions are similar), but hopefully that will come soon.

It’s also worth considering exactly what permissions are required where. If you make calls to the Graph via a back-end API, then maybe SPFx only needs permissions to call *your* API, and the separate AAD app registration used by your back-end has the permissions to take real action. Obviously that can reduce the surface area considerably.

Either way, isolated web parts can play an important role in balancing security with functionality in Office 365. I'll publish the full source code for my Teams Creator web part and Azure Function API in a future article.

Further reading:
https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/isolated-web-parts

No comments: