My trigger was actually a classsic 'admin' versus 'dev' mindset: I wanted to “play” with the new Microsoft Graph Toolkit 2.0, and used Mgt-PersonCard in a custom SPFx webpart. Deployed it to a development/playground site in SharePoint production tenant, as only there is a filled User Profile store to fully experience all the capabilities delivered in the Person-Card component. Asked the Azure tenant admin to approve the API Permissions that Mgt controls typical require, and in all the Microsoft provided code examples for simplicity sake are requested org-wide. And got the door slammed back in my face: sorry, not going to grant this [are you insane for even dare to ask??]
At first surprised and disappointed on this rejection, I again looked into API Permissions for understanding. I started with questioning some Microsoft contacts on what Microsoft recommended best-pracice in this is. Not suprising, the consultancy answer is on level of “it depends” and “customers must consider and decide for themselves”. So that is what I continued with: deep-dive to understand about how org-wide can be risky, and about the balance between strict security control/mitigation, versus developer convenience.
The happy, unworried flow...
I started with reading again the Microsoft Docs on Connect to Azure AD-secured APIs in SharePoint Framework solutions. This is a typical example of Microsoft documentation in which simplicitly prevails above the more complex story. Yet some credits deserved, in the Considerations section there is some minor warning about Granted permissions apply to all solutions.Happy indeed, for malicious code...
But what does this actually mean, how can 'any solution in tenant' (mis)use the Azure AD permissions granted to the webpart you deployed in AppCatalog? For understanding of this, another Microsoft Docs gives more insight: Connect to API secured with Azure Active Directory; in the section 'Azure AD authorization flows':"Client-side web applications are implemented using JavaScript and run in the context of a browser. These applications are incapable of using a client secret without revealing it to users. Therefore these applications use an authorization flow named OAuth implicit flow to access resources secured with Azure AD. In this flow, the contract between the application and Azure AD is established based on the publicly known client ID and the URL where the application is hosted"In essence this means that all what is needed for arbitrary script code to reuse Azure AD permissions granted on org-wide level, is the public client ID or name of an approved application, and the script must be loaded in the browser on the same domain as that approved application. The clientside script code can then itself setup OAuth Implicit flow for the public client ID or name. The script does not have to be SPFx code itself, in the ultimate situation it can implement OAuth Implicit flow itself on raw OAuth protocol level. The lack of MSAL.js or even ADAL.js does not scare hackers away, they have rich toolkits to help them setup attacks. And the attack can even be simpler: in case the malicious code is included in the browser DOM where also at run and render time a SPFx control is loaded, then it can read/steal in the DOM the access token returned to that SPFx control loaded in the same domain. Awareness on this in Considerations when using OAuth implict flow in client-side webparts:
"Web parts are a part of the page and, unlike SharePoint Add-ins, share DOM and resources with other elements on the page. Access tokens that grant access to resources secured with Azure AD, are retrieved through a callback to the same page where the web part is located. That callback can be processed by any element on the page. Also, after access tokens are processed from callbacks, they're stored in the browser's local storage or session storage from where they can be retrieved by any component on the page. A malicious web part could read the token and either expose the token or the data it retrieved using that token to an external service."
Is there real risk?
A typical 'dev' response is that there is no real security risk of leaking unauthorized information. After all, the client side code can only access data and functions for which the logged-on user is allowed, thus is already entitled to access. Fair point, but the fundamental difference is in the awareness of the logged-on user that data is retrieved under his/her identity. For 'safe' code, the user is aware that data is retrieved; that is exact the business purpose why (s)he is using this trusted control. But consider malicious code that was downloaded by a business user from internet to deliver that one convenient capability (e.g. draw a nice looking graph). The business user is not aware in case that convenient library underwater misuses the OAuth accesstoken granted to e.g. invoke the Graph API for reading person data from User Profiles, and leak that data to social or even dungeon endpoints where you definitely don't want this company related data to land.Protection by isolation [social distancing]
How do SPFx isolated webparts protect against this global allowed access risk? In essence very simple: by isolating the Azure AD / API permissions approval to only the SPFx solution that requested it, and were approved / trusted by the Azure admin tenant. On browser operation level this isolation is delivered by each isolated SPFx webpart in its own unique solution-specific domain, hosted in an iframe. Other code cannot on browser nor DOM level be loaded into such iframe isolated domain, and thus cannot piggy-back on the contract of this approved SPFx solution. Loaded from different url, and the browser protects against reading the accesstoken from the iframe.And a nice additional advantage of SPFx isolated webparts is that in API Permissions administration is logged and thus visible per SPFx solution what permissions are requested. This visibility is lacking with org-wide permissions: an admin (Azure nor SharePoint) cannot tell which SPFx solutions requested the API permissions. And more dangerous: there is no indication on which code (SPFx webparts, other libraries) is using the org-wide approved permissions.
Understanding summed up in bullets
- Azure AD is used to for access-control to data (Microsoft 365) and applications (Microsoft 365 + other applications)
- Azure API permissions (‘scopes’) are used to allow Azure AD authenticated clients, specific access to Azure AD protected resources
- JavaScript code running in browser uses OAuth implicit flow to authenticate on-behalf of the logged-on user to Azure AD, and via approved Azure API permissions is permitted to do actions against the Azure AD protected resource
- Org-Wide allowed API permissions are available for any JavaScript code that is executed in the runtime context of the ASML SharePoint domain, and that applies OAuth implicit flow against Azure AD (OAuth access token is returned within https://<tenant>.sharepoint.com global context)
- There is no visibility in Azure AD of the usage-context for which org-wide API Permissions are required → identity of specific SPFx controls that request them at deployment time, is not administrated
- On Classic Sites: via script injection, whatever library can be downloaded from internet by business users themselves, without IT involvement nor awareness. Malicious code is on Azure AD level enabled to misuse API permissions that are permitted on org-wide level. Without the logged-on user being aware, the downloaded JavaScript library can access the protected resource on behalf of the logged-on user, and “do malicious / bad things’ with the data that can be retrieved. E.g. send out the data to an extern location (social channels, business competitor, ...)
- On Modern Sites: by default the business user are disallowed to insert / inject arbitrary code themselves. Code can only be uploaded as SPFx code in AppCatalog:
- Tenant AppCatalog: managed by IT
- Site AppCatalog: managed by business self (SCA). IT controls whether a Site AppCatalog is delivered on a site, only on good business motivation; and knowledgeable + trusted developers are involved
- Be considerable: PnP community (*) is delivering the Modern Script Editor SPFx webpart. A convenient webpart that enables fast delivery [rapid prototyping] of SharePoint Modern customization, without the need to immediate comply to all the 'SPFx development + deployment hashhle'. But be aware that once you allow this SPFx webpart in your tenant, you are opening also in your restricted Modern SharePoint context 'the box of pandora' that from then enables the business users to inject just any arbitrary script library for runtime execution in SharePoint pages.
- (*) Initial version of Modern Script Editor WebPart is build and delivered by Mikael Svenson, and given as a gift to the SharePoint community. He build this before joining Microsoft; and it may not in any way be regarded as a Microsoft supported + approved solution.
- Native SharePoint (Online) API is itself without Azure AD access-control, and can be invoked without need of approved API permissions. Thus also by code that is not running in context of SharePoint page; e.g. running from another webapplication platform, local running scripting, … This is an inherent security leak / issue in SharePoint Online API, IT not in control to close / improve that
- That the older SharePoint API does not provide the option to improve / control the usage, is not a reason to allow same freedom and flexibility on Azure AD protected APIs, of which Graph API is a significant one. Note: (exposure) scope of Graph API extends to much more then only SharePoint Online: also Mail, Teams, Groups, ….