Dynamics 365 and Node.js integration using the Web API

I wrote a blog post in early 2015 that showed how to access the Dynamics CRM organization data service from a Node.js application. Today I will show an easy way to retrieve data from a Dynamics 365 Online organization in a Node.js application using the Web API.

Unlike the CRM organization service, the Dynamics 365 Web API does not allow you to authenticate directly with a user name and password. Instead you have to authenticate using OAuth to get a token, and then you pass that token to the Web API. Microsoft has created the "Windows Azure Active Directory Authentication Library (ADAL) for Node.js" that can be used to get an OAuth2 token, but in my sample today, I will be making the token request without using ADAL.

Before you begin, you need to make sure you have registered an application in Azure Active Directory that can access your Dynamics 365 online organization. Follow the instructions on this page to register a new Dynamics 365 application. Scroll down to the "Register an application with Microsoft Azure" section and make sure you register the application as a native client application instead of a web application.

After your application is registered, you need to find the endpoint where you can post an OAuth token request.

  1. If you are using the classic Azure management portal, you should click the "develop applications" button in the "I want to" section. Then click the "View authentication and authorization endpoints" link. You should then see a list of various endpoints for your tenant. Copy the OAuth 2.0 token endpoint value.

  2. If you are using the new Azure management portal, once you have your tenant selected in the AD management blade, select "app registrations" on the left and "endpoints" on the top. You should then see your tenant's endpoints to the right. Copy the OAuth 2.0 token endpoint value.

The OAuth token endpoint should look like this https://login.windows.net/SOME_GUID_VALUE/oauth2/token.

Once you have your OAuth token endpoint and your application client id, you can prepare a client application. Here's a sample Node.js application I wrote to retrieve contacts from the Web API and display their names.

'use strict';
var https = require('https');

//set these values to retrieve the oauth token
var crmorg = 'https://CRMORG...dynamics.com';
var clientid = 'CLIENT ID FROM EARLIER';
var username = 'CRM USERNAME';
var userpassword = 'CRM PASSWORD';
var tokenendpoint = 'OAUTH TOKEN ENDPOINT FROM EARLIER';

//set these values to query your crm data
var crmwebapihost = 'CRMORG.api.crm.dynamics.com';
var crmwebapipath = '/api/data/v8.2/contacts?$select=fullname,contactid'; //basic query to select contacts

//remove https from tokenendpoint url
tokenendpoint = tokenendpoint.toLowerCase().replace('https://','');

//get the authorization endpoint host name
var authhost = tokenendpoint.split('/')[0];

//get the authorization endpoint path
var authpath = '/' + tokenendpoint.split('/').slice(1).join('/');

//build the authorization request
//if you want to learn more about how tokens work, see IETF RFC 6749 - https://tools.ietf.org/html/rfc6749
var reqstring = 'client_id='+clientid;
reqstring+='&resource='+encodeURIComponent(crmorg);
reqstring+='&username='+encodeURIComponent(username);
reqstring+='&password='+encodeURIComponent(userpassword);
reqstring+='&grant_type=password';

//set the token request parameters
var tokenrequestoptions = {
	host: authhost,
	path: authpath,
	method: 'POST',
	headers: {
		'Content-Type': 'application/x-www-form-urlencoded',
		'Content-Length': Buffer.byteLength(reqstring)
	}
};

//make the token request
var tokenrequest = https.request(tokenrequestoptions, function(response) {
	//make an array to hold the response parts if we get multiple parts
	var responseparts = [];
	response.setEncoding('utf8');
	response.on('data', function(chunk) {
		//add each response chunk to the responseparts array for later
		responseparts.push(chunk);		
	});
	response.on('end', function(){
		//once we have all the response parts, concatenate the parts into a single string
		var completeresponse = responseparts.join('');
		//console.log('Response: ' + completeresponse);
		console.log('Token response retrieved . . . ');
		
		//parse the response JSON
		var tokenresponse = JSON.parse(completeresponse);
		
		//extract the token
		var token = tokenresponse.access_token;
		
		//pass the token to our data retrieval function
		getData(token);
	});
});
tokenrequest.on('error', function(e) {
	console.error(e);
});

//post the token request data
tokenrequest.write(reqstring);

//close the token request
tokenrequest.end();


function getData(token){
	//set the web api request headers
	var requestheaders = { 
		'Authorization': 'Bearer ' + token,
		'OData-MaxVersion': '4.0',
		'OData-Version': '4.0',
		'Accept': 'application/json',
		'Content-Type': 'application/json; charset=utf-8',
		'Prefer': 'odata.maxpagesize=500',
		'Prefer': 'odata.include-annotations=OData.Community.Display.V1.FormattedValue'
	};
	
	//set the crm request parameters
	var crmrequestoptions = {
		host: crmwebapihost,
		path: crmwebapipath,
		method: 'GET',
		headers: requestheaders
	};
	
	//make the web api request
	var crmrequest = https.request(crmrequestoptions, function(response) {
		//make an array to hold the response parts if we get multiple parts
		var responseparts = [];
		response.setEncoding('utf8');
		response.on('data', function(chunk) {
			//add each response chunk to the responseparts array for later
			responseparts.push(chunk);		
		});
		response.on('end', function(){
			//once we have all the response parts, concatenate the parts into a single string
			var completeresponse = responseparts.join('');
			
			//parse the response JSON
			var collection = JSON.parse(completeresponse).value;
			
			//loop through the results and write out the fullname
			collection.forEach(function (row, i) {
				console.log(row['fullname']);
			});
		});
	});
	crmrequest.on('error', function(e) {
		console.error(e);
	});
	//close the web api request
	crmrequest.end();
}

When I run this from my local PC against a my Dynamics 365 org with sample data installed, I get this output:

Although my sample application isn't fancy, it shows that authenticating to Dynamics 365 and retrieving data without requiring special libraries is now much easier than it used to be in the pre-CRM 2016 days, and that is very exciting.

What do you think? Please let me know your thoughts in the comments.