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'];

?>