Calling Office 365 graph API from PHP - App based authentication

Looking at difficulties I faced while making a call to Office 365 Graph API / SharePoint Graph API, I thought of making an article explaining challenges and their resolutions. Here we go!

The code is VERY simply written and it doesn't have any kind of advanced coding standard to make things easier for reader (Reason being I am not a PHP guy and I can understand what it feels to see a much advance code and you kind of not understand much part of it :)


The article is divided into 5 parts;

  1. Registering an app to office 365

  2. Grating access to app to your office 365 tenancy

  3. Retrieve client Id and key 

  4. Retrieving auth token from office 365 tenancy 

  5. Making a graph call


Step 1:  Registering an app to office 365

I am not going to be in much detail about creating an app inside Azure AD of your 365 environment, however I would give overview of it;
  1. Go to your Azure Management Portal using your Office 365 tenancy global admin credentials.
  2. Click 'Active Directory' on the left menu, then click on the directory for your Office 365 tenancy.
  3. click 'Applications' from top navigation bar.
  4. Click 'Add' from bottom navigation bar.
  5. You shall be presented with 'What you want to do' page, click 'Add an application my organization is developing'.
    On the Tell us about your application page, specify 'PhpGraphAPISampleApp' (or whatever you want to call it) for the application name and select 'NATIVE CLIENT APPLICATION' and select Native App Option.
  6. Click on next arrow (Bottom Right).
  7. On 'Application information' page, specify a Redirect URI, example 'http://localhost/PhpGraphAPISampleApp'. 
 If you want more details about registering app, please refer: https://msdn.microsoft.com/en-us/office/office365/howto/getting-started-office-365-apis 

 Step 2: Grating access to app to your office 365 tenancy

You need to be much specific about what access you would want the APP to get on your tenancy. Obviously its not a good idea to grant extra access which app doesn't want. Here are the steps you need to follow;
  1. Once you finish App registration process (After step 7 above), you shall be redirected to 'Quick Start' page, please click on 'Configure' from top navigation bar.
  2. In permissions to other applications, click 'Add application'.
  3. Here you need to select what access you would want form your office 365 tenancy. For this example, I am going to do operations on users of that office 365 tenancy which means the app needs access to manage users in that Azure AD tenancy of office 365.
  4. For that, select "Microsoft Azure Active Directory" app and add it (Click on true sign arrow on bottom right of popup).
  5.  Once you add that application, you should be able to grant application and delegated permission. 
  6. Select "Read and Write directory data" in Application permissions. 
  7. Click on Save.
  8. Done!

Step 3: Retrieve client Id and key 

As a next step, we need to fetch the app client ID and key which is a kind of credential of your app. 

On the same configuration page, you should be able to see the client ID field which will show the unique GUID. Please copy that somewhere as its going to be used inside the PHP code.

For generation of key, you should see a heading called "Keys" on the same configuration page. You will have to give the expiry time which could be between "1 Year" or "2 Years". Once you select any duration, you will have click on save.

Once you save the settings, you can see the generated Key. Please copy it somewhere as the client ID and key is something which will be used in your code as credentails.

Note: The key is only visible after you save the settings, and will disappear next time onwards, so you better save it somewhere, or you will have to generate new key. 

Step 4: Retrieving auth token from office 365 tenancy 

Finally we are done with the office 365 app creation and permissions. Believe me its a job of no more than 10 minutes! 

Now since the app is registered and permission is granted, we need to use it to do our operations. As a first step, we need to hit to Microsoft Auth API to get the bearer token by using our app client id and key. The call to do so is;

https://login.windows.net/.onmicrosoft.com/oauth2/token?api-version=1.6

The result you will receive will be something like;

{"token_type":"Bearer","expires_in":"3600","ext_expires_in":"0","expires_on":"1492755514","not_before":"1492751614","resource":"https://graph.microsoft.com",
"access_token":""}


Here is the sample code;

 function getMSAuthenticationHeader($appTenantDomainName){
     
      try{
        $appPrincipalId = '';
        $password = '';
        // Password
        $clientSecret = urlencode($password);
       
        // Information about the resource we need access for which in this case is graph.
        $graphId = 'https://graph.windows.net';
        $protectedResourceHostName = 'graph.windows.net';
        $graphPrincipalId = urlencode($graphId);
       
       
        // Information about the app
        $clientPrincipalId = urlencode($appPrincipalId);
       
        // Construct the body for the STS request
        $authenticationRequestBody = 'grant_type=client_credentials&client_secret='.$clientSecret
                  .'&'.'resource='.$graphPrincipalId.'&'.'client_id='.$clientPrincipalId;
        //return $authenticationRequestBody;
        //Using curl to post the information to STS and get back the authentication response   
        $ch = curl_init();
        // set url
        $stsUrl = 'https://login.windows.net/'.$appTenantDomainName.'.onmicrosoft.com/oauth2/token?api-version=1.0';       
       
        curl_setopt($ch, CURLOPT_URL, $stsUrl);
        // Get the response back as a string
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        // Mark as Post request
        curl_setopt($ch, CURLOPT_POST, 1);
        // Set the parameters for the request
        curl_setopt($ch, CURLOPT_POSTFIELDS,  $authenticationRequestBody);
       
        // By default, HTTPS does not work with curl.
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        // read the output from the post request
        $output = curl_exec($ch);        
        // close curl resource to free up system resources
        curl_close($ch);     
        //return $output;
        // decode the response from sts using json decoder
        $tokenOutput = json_decode($output);
        //return $tokenOutput;
        if($tokenOutput->{'error_description'} != "")
        {
          return 'invalidtenancy';
        }
        else
        {
          return 'Authorization:' . $tokenOutput->{'token_type'}.' '.$tokenOutput->{'access_token'};
        }
        }
        catch (Exception $e) {
          return $e->getMessage();

          }
    }

 Step 5: Making a graph api call

 Now since we have retrieved the token and we know when it is going to expire, lets use it to do the magic!

I will show how to get all users. 

Call endpoint:   

https://graph.windows.net//users?api-version=1.6

Jumping on code directly;


//get the basic tenancy info to make sure the tenancy has been granted access
  function getAllUsers($appTenantDomainName,$authToken){
      try{
           // initiaze curl which is used to make the http request.
            $ch = curl_init();
            echo $authToken;
            // Add authorization and other headers. Also set some common settings.
               // Add authorization header, request/response format header( for json) and a header to request content for Update and delete operations. 
            curl_setopt($ch, CURLOPT_HTTPHEADER, array($authToken,  'Accept:application/json;odata=minimalmetadata',
                                                        'Content-Type:application/json;odata=minimalmetadata', 'Prefer:return-content'));
            // Set the option to recieve the response back as string.
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            // By default https does not work for CURL.
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            // set url
            $feedURL = "https://graph.windows.net/".$appTenantDomainName."/users?api-version=1.6";
           // echo $feedURL;
            curl_setopt($ch, CURLOPT_URL, $feedURL);
            //Enable fiddler to capture request
            //curl_setopt($ch, CURLOPT_PROXY, '127.0.0.1:8888');

            // $output contains the output string
            $output = curl_exec($ch);
            //get the status code
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            //echo $httpCode;
            $jsonOutput = json_decode($output);
            //echo $output;
            curl_close($ch);
            if($httpCode != 200){
              return false;
            }
            else
            {
            return true;
            }
            // close curl resource to free up system resources
           
           
            // There is a field for odata metadata that we ignore and just consume the value
            //return $jsonOutput->{'value'};
        }
        catch (Exception $e) {
          return $e->getMessage();

          }
    }
Expected output;

{
    "odata.metadata": "https://graph.windows.net/.onmicrosoft.com/$metadata#directoryObjects/Microsoft.DirectoryServices.User",
    "value": [
        {
            "odata.type": "Microsoft.DirectoryServices.User",
            "objectType": "User",
            "objectId": "b8e156e7-b83f-4f93-8264-533b5f01e6be",
            "deletionTimestamp": null,
            "accountEnabled": true,
            "signInNames": [],
            "assignedLicenses": [
                {
                    "disabledPlans": [],
                    "skuId": "189a915c-fe4f-4ffa-bde4-85b9628d07a0"
                }
            ],
            "assignedPlans": [
                {
                    "assignedTimestamp": "2017-04-14T20:17:08Z",
                    "capabilityStatus": "Enabled",
                    "service": "SharePoint",
                    "servicePlanId": "a361d6e2-509e-4e25-a8ad-950060064ef4"
                },
                {
                    "assignedTimestamp": "2017-04-14T20:17:08Z",
                    "capabilityStatus": "Enabled",
                    "service": "SharePoint",
                    "servicePlanId": "527f7cdd-0e86-4c47-b879-f5fd357a3ac6"
                },
                {
                    "assignedTimestamp": "2017-04-14T20:17:08Z",
                    "capabilityStatus": "Enabled",
                    "service": "exchange",
                    "servicePlanId": "efb87545-963c-4e0d-99df-69c6916d9eb0"
                },
                {
                    "assignedTimestamp": "2017-04-14T20:17:08Z",
                    "capabilityStatus": "Enabled",
                    "service": "MicrosoftCommunicationsOnline",
                    "servicePlanId": "0feaeb32-d00e-4d66-bd5a-43b5b83db82c"
                },
                {
                    "assignedTimestamp": "2017-04-14T20:17:08Z",
                    "capabilityStatus": "Enabled",
                    "service": "MicrosoftOffice",
                    "servicePlanId": "43de0ff5-c92c-492b-9116-175376d08c38"
                },
                {
                    "assignedTimestamp": "2017-04-14T20:17:08Z",
                    "capabilityStatus": "Enabled",
                    "service": "Sway",
                    "servicePlanId": "a23b959c-7ce8-4e57-9140-b90eb88a9e97"
                },
                {
                    "assignedTimestamp": "2017-04-14T20:17:08Z",
                    "capabilityStatus": "Enabled",
                    "service": "ProjectWorkManagement",
                    "servicePlanId": "b737dad2-2f6c-4c65-90e3-ca563267e8b9"
                },
                {
                    "assignedTimestamp": "2017-04-14T20:17:08Z",
                    "capabilityStatus": "Enabled",
                    "service": "TeamspaceAPI",
                    "servicePlanId": "57ff2da0-773e-42df-b2af-ffb7a2317929"
                },
                {
                    "assignedTimestamp": "2017-04-14T20:17:08Z",
                    "capabilityStatus": "Enabled",
                    "service": "PowerAppsService",
                    "servicePlanId": "c68f8d98-5534-41c8-bf36-22fa496fa792"
                },
                {
                    "assignedTimestamp": "2017-04-14T20:17:08Z",
                    "capabilityStatus": "Enabled",
                    "service": "ProcessSimple",
                    "servicePlanId": "76846ad7-7776-4c40-a281-a386362dd1b9"
                }
            ],
            "city": null,
            "companyName": null,
            "country": "IN",
            "creationType": null,
            "department": null,
            "dirSyncEnabled": null,
            "displayName": "Nitin K",
            "facsimileTelephoneNumber": null,
            "givenName": "Nitin",
            "immutableId": null,
            "isCompromised": null,
            "jobTitle": null,
            "lastDirSyncTime": null,
            "mail": "nitink@",
            "mailNickname": "nitink",
            "mobile": null,
            "onPremisesSecurityIdentifier": null,
            "otherMails": [
                "nitink@xyz.in"
            ],
            "passwordPolicies": null,
            "passwordProfile": null,
            "physicalDeliveryOfficeName": null,
            "postalCode": null,
            "preferredLanguage": "en-US",
            "provisionedPlans": [
                {
                    "capabilityStatus": "Enabled",
                    "provisioningStatus": "Success",
                    "service": "exchange"
                },
                {
                    "capabilityStatus": "Enabled",
                    "provisioningStatus": "Success",
                    "service": "MicrosoftCommunicationsOnline"
                },
                {
                    "capabilityStatus": "Enabled",
                    "provisioningStatus": "Success",
                    "service": "exchange"
                },
                {
                    "capabilityStatus": "Enabled",
                    "provisioningStatus": "Success",
                    "service": "SharePoint"
                },
                {
                    "capabilityStatus": "Enabled",
                    "provisioningStatus": "Success",
                    "service": "SharePoint"
                }
            ],
            "provisioningErrors": [],
            "proxyAddresses": [
                "SMTP:nitink@"
            ],
            "refreshTokensValidFromDateTime": "2017-04-14T20:16:39Z",
            "showInAddressList": null,
            "sipProxyAddress": "nitink@",
            "state": null,
            "streetAddress": null,
            "surname": "K",
            "telephoneNumber": null,
            "usageLocation": "IN",
            "userPrincipalName": "nitink@",
            "userType": "Member"
        }
    ]
}

Done! :)

Dont be afraid of long article, much space is taken by code samples :)

Credit: https://github.com/microsoftgraph/php-connect-rest-sample 

Comments

Popular Posts