How to query an on-premises Dynamics CRM from a Web App (Node/Express) How to query an on-premises Dynamics CRM from a Web App (Node/Express) node.js node.js

How to query an on-premises Dynamics CRM from a Web App (Node/Express)


We had this similar situation.Our Organization is OnPrem 8.2. It is accessible via VPN or from Home Network.If you look at the problem in very basic layman's way, our CRM cannot be reached from outside.

What we did was

  1. We created WebAPI for Action from CRM.

  2. We exposed this WebAPI via additional Port to outer world.

  3. We added this WebAPI in IIS as a service.

  4. But we made sure This WebAPI was accessible only via specific userName and Passoword which we created in our Web.config File.

  5. In Background what we did was created Action.

  6. Action in Turn will run Plugin and will Return Data as per asked i.e WebAPI url can be modified. For ex: .../acounts will return for Account entity Provided you had the logic built in your plugin.

    Please do not confuse this with Dynamics CRM OOB WebAPI. What I mean is that creating our own API and add this as a service in IIS with it's own Username and password.

I assume this will give you at-least some hint in which direction to look into.


So... I managed to get this off the ground by reverse engineering the browsers approach to authenticating :) No proxy or Azure nonsense!

I am now directly authenticating with our fs endpoint and parsing the resulting SAML response and using the cookie it provides... which works a treat.

NB: The code below was just knocked up in my Node scratch pad, so it's a mess. I might tidy it up and post a full write up at some point, but for now, if you use any of this code you will want to refactor appropriately ;)

let ADFS_USERNAME = '<YOUR_ADFS_USERNAME>'let ADFS_PASSWORD = '<YOUR_ADFS_PASSWORD>'let httpntlm = require('httpntlm')let ntlm = httpntlm.ntlmlet lm = ntlm.create_LM_hashed_password(ADFS_PASSWORD)let nt = ntlm.create_NT_hashed_password(ADFS_PASSWORD)let cookieParser = require('set-cookie-parser')let request = require('request')let Entity = require('html-entities').AllHtmlEntitieslet entities = new Entity()let uri = 'https://<YOUR_ORGANISATIONS_DOMAIN>/adfs/ls/wia?wa=wsignin1.0&wtrealm=https%3a%2f%2f<YOUR_ORGANISATIONS_CRM_URL>%2f&wctx=rm%3d1%26id%3d1fdab91a-41e8-4100-8ddd-ee744be19abe%26ru%3d%252fdefault.aspx%26crmorgid%3d00000000-0000-0000-0000-000000000000&wct=2019-03-12T11%3a26%3a30Z&wauth=urn%3afederation%3aauthentication%3awindows&client-request-id=e737595a-8ac7-464f-9136-0180000000e1'let apiUrl = 'https://<YOUR_ORGANISATIONS_CRM_URL>/api/data/v8.2/'let crm = 'https://<YOUR_ORGANISATIONS_CRM_URL>'let endpoints = {  INCIDENTS: `${apiUrl}/incidents?$select=ticketnumber,incidentid,prioritycode,description`,  CONTACTS: `${apiUrl}/contacts?$select=fullname,contactid`}httpntlm.get({  url: uri,  username: ADFS_USERNAME,  lm_password: lm,  nt_password: nt,  workstation: '',  domain: ''}, function (err, res) {  if (err) return err  // this looks messy but is getting the SAML1.0 response ready to pass back as form data in the next request  let reg = new RegExp('<t:RequestSecurityTokenResponse([\\s\\S]*?)<\/t:RequestSecurityTokenResponse>')  let result = res.body.match(reg)  let wresult = entities.decode(result[ 0 ])  reg = new RegExp('name="wctx" value="([\\s\\S]*?)" /><noscript>')  result = res.body.match(reg)  let wctx = entities.decode(result[ 1 ])  let payload = {    wctx: wctx,    wresult: wresult  }  getValidCookies(payload)    .then(cookies => {      getIncidents(cookies)        .then(contacts => {          console.log('GOT INCIDENTS', contacts)        })    })})getValidCookies = function (payload) {  return new Promise((resolve, reject) => {    let options = {      method: 'POST',      url: crm,      headers: {        'Content-Type': 'application/x-www-form-urlencoded'      },      form: {        'wa': 'wsignin1.0',        'wresult': payload.wresult,        'wctx': payload.wctx      }    }    request(options, (error, response, body) => {      let requiredCookies = []      let cookies = cookieParser.parse(response)      cookies.forEach(function (cookie) {        if (cookie.name === 'MSISAuth' || cookie.name === 'MSISAuth1') {          requiredCookies.push(`${cookie.name}=${cookie.value}`)        }      })      resolve(requiredCookies)    })  })}getIncidents = function (cookies) {  return new Promise((resolve, reject) => {    let options = {      method: 'GET',      url: endpoints.INCIDENTS,      headers: {        'Cookie': cookies.join(';')      }    }    request(options, (error, response, body) => {      resolve(body)    })  })}getContacts = function (cookies) {  return new Promise((resolve, reject) => {    let options = {      method: 'GET',      url: endpoints.CONTACTS,      headers: {        'Cookie': cookies.join(';')      }    }    request(options, (error, response, body) => {      resolve(body)    })  })}