Connecting to CDS with native PHP

The Microsoft Common Data Service is the underlying data layer that was previously Dynamics CRM and then Dynamics 365 Customer Engagement. The correct way to integrate with modern applications is to use application users. These are registered applications in Azure Active Directory that are granted access to various APIs in the Microsoft ecosystem.

A lot of online documentation exists on writing client applications that use the Active Directory Authentication Library (ADAL) or newer Microsoft Authentication Library (MSAL). Headless server-to-server connections; not so much. Microsoft does provide some libraries you can use, but PHP appears to be lacking.

The below code retrieves a token from the token Azure Active Directory token service. No 3rd party packages required. For clarity, the code snippet contains no error handling and is a straight set of lines to the solution, without creating a series of reusable functions.

I am assuming prior knowledge about creating a registered application, granting it the appropriate organisation-wide access, and collecting the necessary details below. Once you have the bearer token, you can connect to the CDS API to manipulate data.

<?php

  // Configuration item
  $tenantId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
  $odataUrl = 'https://instancehost/api/data/v9.1/';
  $appId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
  $appSecret = 'x';

  // Build other required items, using configuration items
  $url = parse_url($odataUrl);
  $scope = $url['scheme'].'://'.$url['host'].'/.default';
  $loginUrl =  'https://login.microsoftonline.com/'
    .tenantId
    .'/oauth2/v2.0/token';
  $authBody = http_build_query([
    'client_id' => $appId,
    'client_secret' => $appSecret,
    'scope' => $scope,
    'grant_type' => 'client_credentials'
  ]);
  $authHeaders = [
    'Content-Type: application/x-www-form-urlencoded',
    'Accept: application/json',
    'Content-Length: '.strlen($authBody)
  ];

  // Connect to the token service to obtain a token
  $ctx = stream_context_create([
    'http' => [
      'method' => 'POST',
      'header' => $authHeaders,
      'protocol_version' => 1.1,
      'ignore_errors' => true,
      'content' => $authBody
    ]
  ]);
  $stream = fopen($loginUrl, 'r', false, $ctx);
  $contents = stream_get_contents($stream);
  fclose($stream);
  $auth = json_decode($contents);

  // Create the authorisation header required
  // Add $authHeader to the request headers against $odataUrl
  $authHeader = 'Authorization: Bearer '.$auth['access_token'];

?>

Angular on Azure Functions – Serverless Hosting

It’s possible to host basic Angular applications together with their backend API, using a combination of Azure Functions and Azure Storage. Azure Storage can host static web content for the frontend, and Azure Functions have HTTP triggers for the backend. To tie it neatly together with a bow, using Azure Functions proxy configuration allows all of it to be accessible through a single URL.

This provides the fundamental components required to host an Angular app with a backend REST API, for minimal hosting costs, when PowerApps don’t quite cut it for your requirements.

The Azure Function will become the public-facing “web site”, and proxy requests for the Angular app to a storage account BLOB container.

To achieve this, create a BLOB container with a Public Access Level of “Blob (anonymous read access for blobs only)” inside an Azure Storage Account. This can be the same storage account that the Function App uses for storage as well.

In your Function App add proxies.json with the following content.

{
  "$schema": "http://json.schemastore.org/proxies",
  "proxies": {
    "root": {
      "matchCondition": {
        "methods": [ "GET" ],
        "route": "/"
      },
      "backendUri": "https://.blob.core.windows.net/app/index.html"
    },
    "rootFiles": {
      "matchCondition": {
        "methods": [ "GET" ],
        "route": "{file}.{ext}"
      },
      "backendUri": "https://.blob.core.windows.net/app/{file}.{ext}"
    },
    "assets": {
      "matchCondition": {
        "methods": [ "GET" ],
        "route": "/assets/{*file}"
      },
      "backendUri": "https://.blob.core.windows.net/app/assets/{file}"
    }
  }
}

Substitute the backendUri values to those that match your Storage Account and BLOB Container. Once done copy your built Angular App into the container. This configuration covers a standard Angular build but will require modification if you have additional directories to “assets”.

Authentication can be applied at the Function App level, which I’ll go through in another post. Azure Active Directory, Facebook, Twitter, Google and Microsoft personal accounts can also be used quite easily.