5
(4)

There are many use cases which you need to call a service/web API from another Web API and it requires to propagate the delegated user identity and permissions through the request chain. OAuth 2.0 On-Behalf-Of flow helps you to authorize access from the gateway to the downstream APIs without losing trace of the user.

In this post I will show you how to use a SharePoint Framework web part as a client application communicates with an Azure Function which acts as a gateway that forwards the requests from to other downstream APIs (which is the Dynamics CRM in this sample).

Azure Function

Let’s start with the Azure Function, go to Azure portal and create a new Function App with following settings.

When it’s ready, to secure the our function, go to Authentication/Authorization settings, turn the App Srvice Authentication on and select “Log in with Azure Active Directory” for authentication.

Under Authentication Providers select Azure Active Directory, select Express and create a new App Registration.

Go to CORS settings and add your SharePoint url to make sure your are allowed to make cross-origin calls from your web part.

Now go to Functions and create an HTTP Trigger function. Before explaining the code, let’s configure the App Registration and we will get back to the function later in this post.

App Registration

Go to App Registrations and find the app you created previously when you configure the Authentication/Authorization, go to API permissions and click on Add a permission. from the list, select the Dynamics CRM, for permission type select Delegated permissions and select user_impersonation as permission.

Grant admin consent fo the requested permissions by clicking on Grant admin consent button.

Go to Certificates & secrets page, and create a new client secret, copy and keep the value as you will need it later on, also go to Overview page and copy Application (client) ID and Directory (tenant) ID.

Obtain an access token via OBO flow

Let’s get back to our function app, and update the configurations by adding 3 entries as following table from the values you previously copied and saved from the app registration.

NameValue
CLIENTID Replace with client id of the app registration
CLIENTSECRET Replace with client secret of the app registration
TENANTID Replace with tenant id of the app registration

In this document you can find useful information on how to construct a HTTP POST request to obtain an access token via OBO flow. I will use MSAL.Net for this sample.

Open Visual Studio and create a new Azure Function project, install “Microsoft.Identity.Client” NuGet package and update the code to:

public static class GetAccounts
{
    [FunctionName("GetAccounts")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
   {
        log.LogInformation("C# HTTP trigger function processed a request.");

        try
        {
           var jwtToken = req.Headers.FirstOrDefault(x => x.Key == "Authorization").Value.FirstOrDefault();
                var resource = req.Query["resource"];
                var token = GetDynamicsCrmAccessToken(jwtToken.Replace("Bearer ",""),resource);
                
            using (HttpClient httpClient = new HttpClient())
            {
                httpClient.BaseAddress = new Uri(resource);
                httpClient.Timeout = new TimeSpan(0, 2, 0);
                httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
                httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
                httpClient.DefaultRequestHeaders.Accept.Add(
                    new MediaTypeWithQualityHeaderValue("application/json"));
                httpClient.DefaultRequestHeaders.Authorization =
                    new AuthenticationHeaderValue("Bearer", token);

                HttpResponseMessage retrieveResponse =
                            await httpClient.GetAsync("/api/data/v9.1/accounts?$select=accountid,name,emailaddress1&$top=5");
                if (retrieveResponse.IsSuccessStatusCode)
                {
                    var results = await retrieveResponse.Content.ReadAsStringAsync();
                    var jsonObject = JsonConvert.DeserializeObject(results);
                    return new OkObjectResult(jsonObject);
                }
                else
                    return new BadRequestErrorMessageResult("");
            }
        }
        catch(Exception ex)
        {
            return new BadRequestErrorMessageResult(ex.Message);
        }
        
    }
    private static string GetDynamicsCrmAccessToken(string jwtToken, string crmUri)
    {
        var crmResourceUri = $"{crmUri}/.default";
        var clientId = Environment.GetEnvironmentVariable("CLIENTID");
        var clientSecret = Environment.GetEnvironmentVariable("CLIENTSECRET");
        var tenantId = Environment.GetEnvironmentVariable("TENANTID");
        List<string> scopes = new List<string>();
        scopes.Add(crmResourceUri);
        var app = ConfidentialClientApplicationBuilder.Create(clientId)
            .WithClientSecret(clientSecret)
            .WithTenantId(tenantId)                
            .Build();
        var userAssertion = new UserAssertion(jwtToken);
        var result = app.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync().GetAwaiter().GetResult();
        return result.AccessToken;

    }

}

In line 12, we get the delegated access token from the Authorization header, and we use that as UserAssertion to obtain another delegated access token for Dynamics CRM. Then we can make a call to the Dynamics CRM endpoint and use the new access token in the Authorization header.

In the above code we get top 5 accounts from Dynamics CRM and return the data to the client application (SPFx web part).

SharePoint Framework Web Part

Using the new AadHttpClient, you can easily connect to APIs secured with Azure AD without having to implement authentication and authorization yourself. Internally, the AadHttpClient implements the Azure AD OAuth flow using ADAL JS by using the SharePoint Online Client Extensibility service principal to obtain a valid access token.

Create a SharePoint Framework web part, to use the AadHttpClient in your SharePoint Framework solution, add the following import clause in your main web part file:

import { AadHttpClient } from "@microsoft/sp-http";

Add OnInit method to the code, Get a new instance of the AadHttpClient, passing the resource to which you want to connect as parameters:

const httpClient: AadHttpClient = await this.context.aadHttpClientFactory.getClient(
    this.properties.appRegistrationId
);

AppRegistrationId is the app client ID you copied previously (alternatively you can also use either the application name or the azure function url).

Now you can securely connect to the azure function and get the result back:

public async getAccounts() {
    const headers: Headers = new Headers();
    headers.append("Accept", "application/json");
    const requestOptions:IHttpClientOptions  = {
        headers          
    };
    const response = await this.aadHttpClient.get(
        `${this.azureFunctionUri}?resource=${this.resourceUri}`,
        AadHttpClient.configurations.v1,
        requestOptions
    );
    const result = await response.text();
    const accounts = JSON.parse(result);
    return accounts.value;
}

You can get the azure function Url from the function page by selecting Get Function Url:

And the resource URI is your Dynamics CRM instance URL (e.x. https://contoso.crm11.dynamics.com).

Finally, open the config/package-solution.json file, to the solution property, add the webApiPermissionRequests property and update it with the permission for your app registration.

"solution": {
    "name": "react-dynamic-365-client-side-solution",
    "id": "5bc3855d-9c59-4322-95e0-78e17dcd809e",
    "version": "1.0.0.1",
    "includeClientSideAssets": true,
    "skipFeatureDeployment": true,
    "isDomainIsolated": false,
    "webApiPermissionRequests": [
      {
        "resource": "OBO-Flow-App",
        "scope": "user_impersonation"
      }
     ]
}

Summary

For middle-tier services which call another services/web APIs it’s important to not lose trace of the current user if your application needs to use delegated permissions, above I explained how you can implement this flow using an Azure Function as middle-tier service and Dynamics CRM API as a downstream API.

As always you can find the source code here.

Sharing is caring!

How useful was this post?

Click on a star to rate it!

Average rating 5 / 5. Vote count: 4

No votes so far! Be the first to rate this post.