Diving into AadHttpClient (with hacking!)

Consuming third party or your own Azure AD protected API from SPFx code is a very common need. I wrote a blog post series on that topic, the first one you can find here. All solutions I covered have their own pros and cons, however the less painful and recommended solution is AadHttpClient (available in SPFx 1.6 and onwards). AadHttpClient approach has less issues and works really good. If you are curious about how it actually works, read the rest of the post. In this post I dive into AadHttpClient architecture, libraries and technologies used, think about security issues and try to bypass (spoiler: successfully) webApiPermissionRequests restrictions in SPFx web parts.

Basically if you want to access your custom API from SPFx web part with AadHttpClient , you need a few things. First of all, you should register a new app in your Azure AD. Secondly, you should add new webApiPermissionRequests node in your package-solution.json. After deployment to an app catalog, you go to your SharePoint admin center and approve permission requests listed in the webApiPermissionRequests node.

NOTE: API management available only in modern SharePoint admin center (you can switch from classic using button in the top):

After performing all of the magic, your code can access Azure AD protected API without issues. The code itself is pretty simple:

this.context.aadHttpClientFactory
     .getClient('<your client id>')
     .then((client: AadHttpClient): void => {
       client
         .get('https://contoso.azurewebsites.net/api/orders', AadHttpClient.configurations.v1)
         .then((response: HttpClientResponse): Promise<Order[]> => {
           return response.json();
         })
         .then((orders: Order[]): void => {
           // process data
         });
     });

If you open network tab in dev tools, you will see, that AadHttpClient attaches bearer JWT token to your ongoing request. How does AadHttpClient generates access token?

Let’s take a look at Azure AD perspective first. In every SharePoint Online tenant, there is an application called “SharePoint Online Client Extensibility Web Application” (I will refer it as SharePoint CE Web App). The application is created as you access web api permission request feature first time. The App Id might be different in you tenant. In my screen it's called "SharePoint Online Client Extensibility", please don't mind, because that's the screen from preview version.

This application is very important, because it serves as a container for all permission requests via “webApiPermissionRequests ”. As soon as you approve permission request in API management, your application will be added in Permissions section for SharePoint CE Web App. At the same time your application will be admin consented, because you approved it from SharePoint admin:

As you have your application approved and listed under permissions in other application, you can use OAuth to generate access tokens for that application. This is exactly what AadHttpClient does.

In a nutshell, AadHttpClient uses adal.js. I talked about adal.js in the blog post series I mentioned at the beginning. That means, that AadHttpClient uses OAuth’s implicit flow (with help of adal.js) in order to generate an access token. adal.js used in AadHttpClient  is slightly modified and adapted for better use in SPFx ecosystem. How does AadHttpClient generate tokens and why it doesn’t have almost all issues we have with other methods (cookie or custom adal.js approach)? In order to generate a token, AadHttpClient  uses hidden iframe, however the iframe is hosted on the same domain. The url is https://your_sharepoint.com/_forms/spfxsinglesignon.aspx. The flow can be described as below:

  1. AadHttpClient requests id token and access token for SharePoint CE application. if access token is not stored in session storage, it adds iframe with src:

https://login.windows.net/<tenant id>/oauth2/authorize?
response_type=id_token+token
&client_id=c58637bb-e2e1-4312-8a00-04b5ffcd3403 <-- SharePoint CE Web App Client Id
&resource=c58637bb-e2e1-4312-8a00-04b5ffcd3403  <-- SharePoint CE Web App Client Id
&redirect_uri=https://mvapps.sharepoint.com/_forms/spfxsinglesignon.aspx
&state=d6620756-2413-44c8-9e12-07af11c9e2e2|c58637bb-e2e1-4312-8a00-04b5ffcd3403
&client-request-id=4f267360-2cf6-4d2b-ab6b-66ca46142c7e
&x-client-SKU=Js
&x-client-Ver=1.0.16
&nonce=8af15875-3e6b-444c-ad4d-67c909a9643b
&prompt=none

Important observation – redirect url is /_forms/spfxsinglesignon.aspx. However this page is in the same domain, as your SharePoint site, thus the code from parent frame (your webpart) can easily access to anything inside iframe. Actually parent frame doesn’t access child iframe. Instead, child hidden iframe has it’s own copy of adal.js, and the only thing it does, it saves received access token in session storage. Session storage is shared across domain, that’s why parent iframe easily extracts access token later on.

If you have multiple web parts, they check the same session storage and don’t generate many hidden iframes. Only one on the first request is generated.

  2. AadHttpClient requests access token for your application id:

https://login.windows.net/<tenant id>/oauth2/authorize?
response_type=token
&client_id=c58637bb-e2e1-4312-8a00-04b5ffcd3403  <-- SharePoint CE Web App Client Id
&resource=6fc2655e-04cd-437d-a50d-0c1a31383775   <-- your application client id, can be MS Graph, etc. 
&redirect_uri=https://mvapps.sharepoint.com/_forms/spfxsinglesignon.aspx
&state=b2631f6c-2df6-4af1-8fbb-d9282b662451|6fc2655e-04cd-437d-a50d-0c1a31383775
&client-request-id=5f318c21-6013-454e-8e1a-135caa280353
&x-client-SKU=Js
&x-client-Ver=1.0.16
&prompt=none
&login_hint=sergei.sergeev@mvapps.onmicrosoft.com
&domain_hint=mvapps.onmicrosoft.com

This time AadHttpClient  requests access token for your application. This token later will be attached to a request to your protected API.

3. AadHttpClient  extracts access token for your application, attaches to Authorization header and finally sends request.

This is how it works. Now when we understand how it works, let’s try to think about possible issues.

> You can bypass “webApiPermissionRequests ” and add required APIs to SharePoint CE explicitly via code

Welcome to the hacking part. I don’t know exact reason why “webApiPermissionRequests ” was added, but you can easily add required permissions using PowerShell or .net code. Probably this node was added in order to give admins a better way to manage everything. So, if you want to hack it, you need a way to manage Oauth2PermissionGrants for a service principal. You can do that via MS Graph, however corresponding endpoints are in beta. That’s why I use Azure AD graph library and .net code.

For example below code is used to add MS Graph permissions to SharePoint CE application:

public static async Task AddSpfxMSGraphPermissions(ActiveDirectoryClient client, string scope)
{
	var spoCeServicePrinicipal = await client.ServicePrincipals
		.Where(sp => sp.AppId == GlobalConstants.SpoCeApplicationId).ExecuteSingleAsync();

	var msGraphServicePrincipal = await client.ServicePrincipals
		.Where(sp => sp.AppId == GlobalConstants.MsGrpaphApplicationId).ExecuteSingleAsync();

	var grants = await client.Oauth2PermissionGrants.ExecuteAsync();
	OAuth2PermissionGrant existingGrant = null;
	foreach (IOAuth2PermissionGrant grant in grants.CurrentPage)
	{
		if (grant.ClientId == spoCeServicePrinicipal.ObjectId &&
			grant.ResourceId == msGraphServicePrincipal.ObjectId)
		{
			existingGrant = (OAuth2PermissionGrant) grant;
		}
	}

	if (existingGrant != null)
	{
		existingGrant.Scope = scope;
		await existingGrant.UpdateAsync();
	}
	else
	{
		var auth2PermissionGrant = new OAuth2PermissionGrant
		{
			ClientId = spoCeServicePrinicipal.ObjectId,
			ConsentType = "AllPrincipals",
			PrincipalId = null,
			ExpiryTime = DateTime.Now.AddYears(10),
			ResourceId = msGraphServicePrincipal.ObjectId,
			Scope = scope
		};

		await client.Oauth2PermissionGrants.AddOAuth2PermissionGrantAsync(auth2PermissionGrant);
	}
}

Full sample is available at GitHub repository.

Check out this animation, which demonstrates how I did it:

> Third party apps can access unwanted data in your tenant

This one is the most trickiest. Imagine, that you have created a few web parts and added a few MS Graph permissions. Later on you installed third party SPFx component. This component immediately gains an access to all granted APIs in your tenant, because it simply can request access token via SharePoint CE application. Probably this is something you don’t want to be happen.

I’ve created an issue in sp-dev-docs repository with questions from this post.

This is it for AadHttpClient. Please remember that it’s still in preview, something might change in future. However I think the current version is pretty close to final.